72 votes

Chargement de fichiers .sql à partir de PHP

Je suis en train de créer une installation script pour une application que je développe et j'ai besoin de créer des bases de données dynamiquement à partir de PHP. J'ai réussi à créer la base de données mais maintenant je dois charger plusieurs fichiers .sql. J'avais prévu d'ouvrir le fichier et d'effectuer une requête mysql_ ligne par ligne - jusqu'à ce que je regarde les fichiers de schéma et réalise qu'il n'y a pas qu'une requête par ligne.

Alors, comment charger un fichier sql à partir de PHP (comme le fait phpMyAdmin avec sa commande d'importation) ?

0 votes

Notamment le code source de phpMyAdmin lui-même :)

0 votes

@Anonymous : Je cherchais la "meilleure" méthode. Cet endroit est censé être un guichet unique pour les réponses (pour l'avenir également) et je pense donc que poser des questions auxquelles on a répondu ailleurs est tout à fait normal. Cependant, j'ai d'abord cherché ailleurs et je n'ai pas trouvé de bonne réponse.

0 votes

RE : phpMyAdmin - sa source est quelque peu utile mais très dépendante de ses autres fonctionnalités et ne convient pas à mes exigences très strictes en termes de délai et de complexité.

80voto

Luis Granja Points 211
$db = new PDO($dsn, $user, $password);

$sql = file_get_contents('file.sql');

$qr = $db->exec($sql);

0 votes

J'utilise cette réponse. Elle a bien fonctionné pour moi sur un script de 643kb. Jusqu'à présent, tout va bien.

6 votes

Et quand le script fait 643mb ? Ou en tout cas, plus grand que max_allowed_packet ?

1 votes

Parfait ! Fonctionne pour moi avec un fichier de 1MB jusqu'à présent.

60voto

PhpBB utilise quelques fonctions pour analyser ses fichiers. Elles sont plutôt bien commentées (quelle exception !) donc vous pouvez facilement savoir ce qu'elles font (j'ai obtenu cette solution de http://www.frihost.com/forums/vt-8194.html ). Voici la solution et je l'ai beaucoup utilisée :

<php
ini_set('memory_limit', '5120M');
set_time_limit ( 0 );
/***************************************************************************
*                             sql_parse.php
*                              -------------------
*     begin                : Thu May 31, 2001
*     copyright            : (C) 2001 The phpBB Group
*     email                : support@phpbb.com
*
*     $Id: sql_parse.php,v 1.8 2002/03/18 23:53:12 psotfx Exp $
*
****************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

/***************************************************************************
*
*   These functions are mainly for use in the db_utilities under the admin
*   however in order to make these functions available elsewhere, specifically
*   in the installation phase of phpBB I have seperated out a couple of
*   functions into this file.  JLH
*
\***************************************************************************/

//
// remove_comments will strip the sql comment lines out of an uploaded sql file
// specifically for mssql and postgres type files in the install....
//
function remove_comments(&$output)
{
   $lines = explode("\n", $output);
   $output = "";

   // try to keep mem. use down
   $linecount = count($lines);

   $in_comment = false;
   for($i = 0; $i &lt; $linecount; $i++)
   {
      if( preg_match("/^\/\*/", preg_quote($lines[$i])) )
      {
         $in_comment = true;
      }

      if( !$in_comment )
      {
         $output .= $lines[$i] . "\n";
      }

      if( preg_match("/\*\/$/", preg_quote($lines[$i])) )
      {
         $in_comment = false;
      }
   }

   unset($lines);
   return $output;
}

//
// remove_remarks will strip the sql comment lines out of an uploaded sql file
//
function remove_remarks($sql)
{
   $lines = explode("\n", $sql);

   // try to keep mem. use down
   $sql = "";

   $linecount = count($lines);
   $output = "";

   for ($i = 0; $i &lt; $linecount; $i++)
   {
      if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
      {
         if (isset($lines[$i][0]) && $lines[$i][0] != "#")
         {
            $output .= $lines[$i] . "\n";
         }
         else
         {
            $output .= "\n";
         }
         // Trading a bit of speed for lower mem. use here.
         $lines[$i] = "";
      }
   }

   return $output;

}

//
// split_sql_file will split an uploaded sql file into single sql statements.
// Note: expects trim() to have already been run on $sql.
//
function split_sql_file($sql, $delimiter)
{
   // Split up our string into "possible" SQL statements.
   $tokens = explode($delimiter, $sql);

   // try to save mem.
   $sql = "";
   $output = array();

   // we don't actually care about the matches preg gives us.
   $matches = array();

   // this is faster than calling count($oktens) every time thru the loop.
   $token_count = count($tokens);
   for ($i = 0; $i &lt; $token_count; $i++)
   {
      // Don't wanna add an empty string as the last thing in the array.
      if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
      {
         // This is the total number of single quotes in the token.
         $total_quotes = preg_match_all("/'/", $tokens[$i], $matches);
         // Counts single quotes that are preceded by an odd number of backslashes,
         // which means they're escaped quotes.
         $escaped_quotes = preg_match_all("/(?&lt;!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches);

         $unescaped_quotes = $total_quotes - $escaped_quotes;

         // If the number of unescaped quotes is even, then the delimiter did NOT occur inside a string literal.
         if (($unescaped_quotes % 2) == 0)
         {
            // It's a complete sql statement.
            $output[] = $tokens[$i];
            // save memory.
            $tokens[$i] = "";
         }
         else
         {
            // incomplete sql statement. keep adding tokens until we have a complete one.
            // $temp will hold what we have so far.
            $temp = $tokens[$i] . $delimiter;
            // save memory..
            $tokens[$i] = "";

            // Do we have a complete statement yet?
            $complete_stmt = false;

            for ($j = $i + 1; (!$complete_stmt && ($j &lt; $token_count)); $j++)
            {
               // This is the total number of single quotes in the token.
               $total_quotes = preg_match_all("/'/", $tokens[$j], $matches);
               // Counts single quotes that are preceded by an odd number of backslashes,
               // which means they're escaped quotes.
               $escaped_quotes = preg_match_all("/(?&lt;!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches);

               $unescaped_quotes = $total_quotes - $escaped_quotes;

               if (($unescaped_quotes % 2) == 1)
               {
                  // odd number of unescaped quotes. In combination with the previous incomplete
                  // statement(s), we now have a complete statement. (2 odds always make an even)
                  $output[] = $temp . $tokens[$j];

                  // save memory.
                  $tokens[$j] = "";
                  $temp = "";

                  // exit the loop.
                  $complete_stmt = true;
                  // make sure the outer loop continues at the right point.
                  $i = $j;
               }
               else
               {
                  // even number of unescaped quotes. We still don't have a complete statement.
                  // (1 odd and 1 even always make an odd)
                  $temp .= $tokens[$j] . $delimiter;
                  // save memory.
                  $tokens[$j] = "";
               }

            } // for..
         } // else
      }
   }

   return $output;
}

$dbms_schema = 'yourfile.sql';

$sql_query = @fread(@fopen($dbms_schema, 'r'), @filesize($dbms_schema)) or die('problem ');
$sql_query = remove_remarks($sql_query);
$sql_query = split_sql_file($sql_query, ';');

$host = 'localhost';
$user = 'user';
$pass = 'pass';
$db = 'database_name';

//In case mysql is deprecated use mysqli functions. 
mysqli_connect($host,$user,$pass) or die('error connection');
mysqli_select_db($db) or die('error database selection');

$i=1;
foreach($sql_query as $sql){
echo $i++;
echo "<br />";
mysql_query($sql) or die('error in query');
}

?>

7 votes

Ce devrait être celui qui est accepté. Ça a marché comme sur des roulettes, merci.

1 votes

C'est la meilleure solution à ce problème que j'ai trouvée.

2 votes

Merci au Groupe phpBB et merci à Abu Sadat d'avoir réussi à remarquer que ce morceau de code est réutilisable pour d'autres projets également. Merci encore

50voto

Jeremy Privett Points 2678

J'ai l'impression que tous ceux qui ont répondu à cette question ne savent pas ce que c'est que d'être un développeur d'applications Web qui permet aux gens d'installer l'application sur leurs propres serveurs. L'hébergement partagé, en particulier, ne vous permet pas d'utiliser le SQL comme la requête "LOAD DATA" mentionnée précédemment. La plupart des hôtes partagés ne vous permettent pas non plus d'utiliser shell_exec.

Maintenant, pour répondre à l'OP, votre meilleure chance est de construire un fichier PHP qui contient vos requêtes dans une variable et qui peut simplement les exécuter. Si vous êtes déterminé à analyser les fichiers .sql, vous devriez regarder dans phpMyAdmin et trouver quelques idées pour obtenir des données des fichiers .sql de cette façon. Regardez les autres applications Web qui ont des installateurs et vous verrez que, plutôt que d'utiliser des fichiers .sql pour leurs requêtes, ils les regroupent dans des fichiers PHP et exécutent chaque chaîne par mysql_query ou tout ce qu'ils doivent faire.

2 votes

Il est vrai que les environnements hébergés sont plus restrictifs. La question de l'OP ne mentionne pas que l'application doit être déployée dans des environnements hébergés. Hmm. La question de l'exécution de scripts SQL en PHP se pose assez souvent pour que ce soit un bon petit projet.

0 votes

Oui, j'allais dire ça - vous ne pouvez pas vous attendre à ce que les gens supposent que vous êtes dans l'environnement le plus restrictif qui soit. Surtout avec les machines virtuelles qui sont proposées partout, tout le monde peut avoir son propre serveur à un coût relativement faible.

4 votes

Je ne vois toujours pas pourquoi il ne peut pas simplement lire le fichier .sql dans une chaîne et l'exécuter avec PDO ou mysqli. C'est comme ça que je fais. PDO et mysqli supportent les requêtes multiples. Certes, je n'ai pas encore exécuté d'énormes fichiers .sql, mais ne pourriez-vous pas simplement augmenter ou supprimer le temps d'exécution maximum de script de PHP ?

30voto

Bill Karwin Points 204877

La solution la plus simple est d'utiliser shell_exec() pour exécuter le client mysql avec le SQL script en entrée. Cela pourrait fonctionner un peu plus lentement parce qu'il faut forker, mais vous pouvez écrire le code en quelques minutes et ensuite retourner travailler sur quelque chose d'utile. Écrire un script PHP pour exécuter n'importe quel script SQL pourrait vous prendre des semaines.

La prise en charge des scripts SQL est plus complexe que ce que les gens décrivent ici, sauf si vous êtes certain que votre script ne contient qu'un sous-ensemble des fonctionnalités des scripts. Vous trouverez ci-dessous quelques exemples de choses qui peuvent apparaître dans un script SQL ordinaire et qui rendent complexe le codage d'un script pour l'interpréter ligne par ligne.

-- Comment lines cannot be prepared as statements
-- This is a MySQL client tool builtin command.  
-- It cannot be prepared or executed by server.
USE testdb;

-- This is a multi-line statement.
CREATE TABLE foo (
  string VARCHAR(100)
);

-- This statement is not supported as a prepared statement.
LOAD DATA INFILE 'datafile.txt' INTO TABLE foo;

-- This statement is not terminated with a semicolon.
DELIMITER //

-- This multi-line statement contains a semicolon 
-- but not as the statement terminator.
CREATE PROCEDURE simpleproc (OUT param1 INT)
BEGIN
  SELECT COUNT(*) INTO param1 FROM foo;
END
// 

Si vous ne prenez en charge qu'un sous-ensemble de scripts SQL, en excluant certains cas particuliers comme ceux mentionnés ci-dessus, il est relativement facile d'écrire un script PHP qui lit un fichier et exécute les instructions SQL dans le fichier. Mais si vous voulez supporter tout script SQL valide, c'est beaucoup plus complexe.


Voir également mes réponses à ces questions connexes :

10voto

phatduckk Points 1291

mysqli peut exécuter plusieurs requêtes séparées par un ;

vous pourriez lire tout le fichier et l'exécuter en une seule fois en utilisant mysqli_multi_query()

Mais, je serai le premier à dire que ce n'est pas la solution la plus élégante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X