web-dev-qa-db-fra.com

Prise en charge de PDO pour plusieurs requêtes (PDO_MYSQL, PDO_MYSQLND)

Je sais que PDO ne prend pas en charge l'exécution de plusieurs requêtes dans une seule instruction. J'ai consulté Google et trouvé quelques articles sur PDO_MYSQL et PDO_MYSQLND.

PDO_MySQL est une application plus dangereuse que toute autre application MySQL traditionnelle. MySQL traditionnel n'autorise qu'une seule requête SQL. Dans PDO_MySQL, cette limitation n'existe pas, mais vous risquez de recevoir plusieurs requêtes.

De: Protection contre l'injection SQL avec PDO et Zend Framework (juin 2010; par Julian)

Il semble que PDO_MYSQL et PDO_MYSQLND prennent en charge plusieurs requêtes, mais je ne parviens pas à trouver plus d'informations à leur sujet. Ces projets ont-ils été abandonnés? Est-il possible d'exécuter plusieurs requêtes à l'aide de PDO?.

95
Gajus

Comme je le sais, PDO_MYSQLND remplacé PDO_MYSQL in PHP 5.3. Ce qui prête à confusion est que ce nom est toujours PDO_MYSQL. Alors maintenant, ND est le pilote par défaut pour MySQL + PDO.

Globalement, pour exécuter plusieurs requêtes à la fois, vous avez besoin de:

  • PHP 5.3+
  • mysqlnd
  • Déclarations préparées émulées. Assure-toi PDO::ATTR_EMULATE_PREPARES est réglé sur 1 (défaut). Sinon, vous pouvez éviter d'utiliser des instructions préparées et utiliser $pdo->exec directement.

Utilisation de exec

$db = new PDO("mysql:Host=localhost;dbname=test", 'root', '');

// works regardless of statements emulation
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

try {
    $db->exec($sql);
}
catch (PDOException $e)
{
    echo $e->getMessage();
    die();
}

Utilisation d'instructions

$db = new PDO("mysql:Host=localhost;dbname=test", 'root', '');

// works not with the following set to 0. You can comment this line as 1 is default
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

try {
    $stmt = $db->prepare($sql);
    $stmt->execute();
}
catch (PDOException $e)
{
    echo $e->getMessage();
    die();
}

Une note:

Lorsque vous utilisez des instructions préparées émulées, assurez-vous que vous avez défini le codage correct (reflétant le codage des données réelles) dans DSN (disponible depuis le 5.3.6). Sinon il peut y avoir une légère possibilité d'injection SQL si un codage impair est utilisé .

132
Sam Dark

Après une demi-journée de manipulation, nous avons découvert que PDO avait un bug où ...

-

//This would run as expected:
$pdo->exec("valid-stmt1; valid-stmt2;");

-

//This would error out, as expected:
$pdo->exec("non-sense; valid-stmt1;");

-

//Here is the bug:
$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");

Il exécuterait le "valid-stmt1;", arrêtez sur "non-sense;" et ne jetez jamais d'erreur. Ne lancera pas le "valid-stmt3;", retourne vrai et mente que tout va bien.

Je m'attendrais à ce qu'il se trompe sur le "non-sense;" mais ça ne marche pas.

Voici où j'ai trouvé cette information: ne requête PDO non valide ne renvoie pas d'erreur

Voici le bogue: https://bugs.php.net/bug.php?id=6161


Donc, j'ai essayé de faire cela avec mysqli et je n'ai pas vraiment trouvé de réponse solide sur son fonctionnement. J'ai donc pensé le laisser ici pour ceux qui veulent l'utiliser.

try{
    // db connection
    $mysqli = new mysqli("Host", "user" , "password", "database");
    if($mysqli->connect_errno){
        throw new Exception("Connection Failed: [".$mysqli->connect_errno. "] : ".$mysqli->connect_error );
        exit();
    }

    // read file.
    // This file has multiple sql statements.
    $file_sql = file_get_contents("filename.sql");

    if($file_sql == "null" || empty($file_sql) || strlen($file_sql) <= 0){
        throw new Exception("File is empty. I wont run it..");
    }

    //run the sql file contents through the mysqli's multi_query function.
    // here is where it gets complicated...
    // if the first query has errors, here is where you get it.
    $sqlFileResult = $mysqli->multi_query($file_sql);
    // this returns false only if there are errros on first sql statement, it doesn't care about the rest of the sql statements.

    $sqlCount = 1;
    if( $sqlFileResult == false ){
        throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], [".$mysqli->errno."]: '".$mysqli->error."' }");
    }

    // so handle the errors on the subsequent statements like this.
    // while I have more results. This will start from the second sql statement. The first statement errors are thrown above on the $mysqli->multi_query("SQL"); line
    while($mysqli->more_results()){
        $sqlCount++;
        // load the next result set into mysqli's active buffer. if this fails the $mysqli->error, $mysqli->errno will have appropriate error info.
        if($mysqli->next_result() == false){
            throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], Error No: [".$mysqli->errno."]: '".$mysqli->error."' }");
        }
    }
}
catch(Exception $e){
    echo $e->getMessage(). " <pre>".$e->getTraceAsString()."</pre>";
}
17

Une approche rapide et sale:

function exec_sql_from_file($path, PDO $pdo) {
    if (! preg_match_all("/('(\\\\.|.)*?'|[^;])+/s", file_get_contents($path), $m))
        return;

    foreach ($m[0] as $sql) {
        if (strlen(trim($sql)))
            $pdo->exec($sql);
    }
}

Divise à des points finaux d'instruction SQL raisonnables. Il n'y a pas de vérification d'erreur, pas de protection contre l'injection. Comprenez votre utilisation avant de l'utiliser. Personnellement, je l'utilise pour créer des fichiers de migration bruts à des fins d'intégration.

4
bishop

Essayez cette fonction: insertion de plusieurs requêtes et de plusieurs valeurs.

function employmentStatus($Status) {
$pdo = PDO2::getInstance();

$sql_parts = array(); 
for($i=0; $i<count($Status); $i++){
    $sql_parts[] = "(:userID, :val$i)";
}

$requete = $pdo->dbh->prepare("DELETE FROM employment_status WHERE userid = :userID; INSERT INTO employment_status (userid, status) VALUES ".implode(",", $sql_parts));
$requete->bindParam(":userID", $_SESSION['userID'],PDO::PARAM_INT);
for($i=0; $i<count($Status); $i++){
    $requete->bindParam(":val$i", $Status[$i],PDO::PARAM_STR);
}
if ($requete->execute()) {
    return true;
}
return $requete->errorInfo();
}
0
hassan b.

Comme des milliers de personnes, je cherche cette question:
Peut exécuter plusieurs requêtes simultanément, et s'il y avait une erreur, aucune ne fonctionnerait, j'allais partout sur cette page
Mais bien que les amis ici aient donné de bonnes réponses, ces réponses n'étaient pas bonnes pour mon problème
J'ai donc écrit une fonction qui fonctionne bien et qui n’a pratiquement aucun problème avec SQL Injection.
Cela pourrait être utile pour ceux qui recherchent des questions similaires alors je les pose ici pour les utiliser.

function arrayOfQuerys($arrayQuery)
{
    $mx = true;
    $conn->beginTransaction();
    try {
        foreach ($arrayQuery AS $item) {
            $stmt = $conn->prepare($item["query"]);
            $stmt->execute($item["params"]);
            $result = $stmt->rowCount();
            if($result == 0)
                $mx = false;
         }
         if($mx == true)
             $conn->commit();
         else
             $conn->rollBack();
    } catch (Exception $e) {
        $conn->rollBack();
        echo "Failed: " . $e->getMessage();
    }
    return $mx;
}

pour utilisation (exemple):

 $arrayQuery = Array(
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("aa1", 1)
    ),
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("bb1", 2)
    )
);
arrayOfQuerys($arrayQuery);

et ma connexion:

    try {
        $options = array(
            //For updates where newvalue = oldvalue PDOStatement::rowCount()   returns zero. You can use this:
            PDO::MYSQL_ATTR_FOUND_ROWS => true
        );
        $conn = new PDO("mysql:Host=$servername;dbname=$database", $username, $password, $options);
        $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo "Error connecting to SQL Server: " . $e->getMessage();
    }

Note:
Cette solution vous aide à exécuter plusieurs instructions en même temps,
Si une instruction incorrecte se produit, aucune autre instruction n'est exécutée.

0
mirzaei.sajad

Essayé code suivant

 $db = new PDO("mysql:Host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Ensuite

 try {
 $db->query('SET NAMES gbk');
 $stmt = $db->prepare('SELECT * FROM 2_1_paidused WHERE NumberRenamed = ? LIMIT 1');
 $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
 }
 catch (PDOException $e){
 echo "DataBase Errorz: " .$e->getMessage() .'<br>';
 }
 catch (Exception $e) {
 echo "General Errorz: ".$e->getMessage() .'<br>';
 }

Et j'ai

DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '/*' LIMIT 1' at line 1

Si ajouté $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); après $db = ...

Puis obtenu une page blanche

Si au lieu de cela SELECT essayait DELETE, alors dans les deux cas une erreur comme

 DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '* FROM 2_1_paidused WHERE NumberRenamed = '¿\' OR 1=1 /*' LIMIT 1' at line 1

Donc, ma conclusion qu'aucune injection possible ...

0
Andris