Question Pourquoi ne devrais-je pas utiliser les fonctions mysql_ * en PHP?


Quelles sont les raisons techniques pour lesquelles on ne devrait pas utiliser mysql_* les fonctions? (par exemple. mysql_query(), mysql_connect() ou mysql_real_escape_string())?

Pourquoi devrais-je utiliser autre chose même s'ils travaillent sur mon site?

Si elles ne fonctionnent pas sur mon site, pourquoi ai-je des erreurs comme

Attention: mysql_connect (): Aucun fichier ou répertoire de ce type


2194
2017-10-12 13:18


origine


Réponses:


L'extension MySQL:

  • N'est pas en développement actif
  • Est officiellement déconseillé à partir de PHP 5.5 (publié en juin 2013).
  • A été supprimé entièrement à partir de PHP 7.0 (sorti en décembre 2015)
    • Cela signifie que dès 31 déc. 2018 il n'existera dans aucune version prise en charge de PHP. Actuellement, il obtient seulement Sécurité mises à jour.
  • Manque une interface OO
  • Ne supporte pas:
    • Requêtes asynchrones non bloquantes
    • Déclarations préparées ou des requêtes paramétrées
    • Procédures stockées
    • Déclarations multiples
    • Transactions
    • La "nouvelle" méthode d'authentification par mot de passe (activée par défaut dans MySQL 5.6, requise dans 5.7)
    • Toutes les fonctionnalités de MySQL 5.1

Comme il est obsolète, l'utiliser rend votre code moins sûr.

Le manque de prise en charge des instructions préparées est particulièrement important car elles fournissent une méthode plus claire et moins sujette aux erreurs pour échapper et citer des données externes que de les échapper manuellement avec un appel de fonction distinct.

Voir la comparaison des extensions SQL.


1808
2018-01-01 11:52



PHP propose trois API différentes pour se connecter à MySQL. Voici les mysql(enlevé à partir de PHP 7), mysqli, et PDO extensions.

le mysql_* les fonctions étaient très populaires, mais leur utilisation n'est plus encouragée. L'équipe de documentation discute de la situation de la sécurité de la base de données et éduque les utilisateurs à s'éloigner de l'extension ext / mysql communément utilisée. php.internals: dépréciation ext / mysql).

Et la dernière équipe de développeurs PHP a pris la décision de générer E_DEPRECATED erreurs lorsque les utilisateurs se connectent à MySQL, que ce soit par mysql_connect(), mysql_pconnect() ou la fonctionnalité de connexion implicite intégrée dans ext/mysql.

ext/mysql était officiellement obsolète depuis PHP 5.5 et a été supprimé depuis PHP 7.

Voir la boîte rouge?

Quand vous partez mysql_* page de manuel de fonction, vous voyez une boîte rouge, expliquant qu'il ne devrait plus être utilisé.

Pourquoi


S'éloigner de ext/mysql n'est pas seulement une question de sécurité, mais aussi d'avoir accès à toutes les fonctionnalités de la base de données MySQL.

ext/mysql a été construit pour MySQL 3.23 et a seulement eu très peu d'ajouts depuis lors tout en gardant la plupart du temps la compatibilité avec cette ancienne version qui rend le code un peu plus difficile à maintenir. Fonctionnalités manquantes non prises en charge par ext/mysql comprendre: (à partir du manuel PHP).

Raison de ne pas utiliser mysql_* fonction:

  • Pas en développement actif
  • Supprimé depuis PHP 7
  • Manque une interface OO
  • Ne prend pas en charge les requêtes asynchrones non bloquantes
  • Ne prend pas en charge les instructions préparées ou requêtes paramétrées
  • Ne prend pas en charge les procédures stockées
  • Ne supporte pas les instructions multiples
  • Ne supporte pas transactions
  • Ne supporte pas toutes les fonctionnalités de MySQL 5.1

Ci-dessus le point cité de la réponse de Quentin

Le manque de prise en charge des instructions préparées est particulièrement important, car elles fournissent une méthode plus claire et moins sujette aux erreurs pour échapper et citer des données externes que de les échapper manuellement avec un appel de fonction distinct.

Voir le comparaison d'extensions SQL.


Suppression des avertissements de dépréciation

Alors que le code est converti en MySQLi/PDO, E_DEPRECATED les erreurs peuvent être supprimées en réglant error_reporting dans php.ini exclure E_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Notez que cela cachera aussi autres avertissements de dépréciation, qui, cependant, peut être pour des choses autres que MySQL. (à partir du manuel PHP)

L'article PDO vs MySQLi: Lequel devriez-vous utiliser? par Dejan Marjanovic vous aidera à choisir.

Et une meilleure façon est PDO, et j'écris maintenant un simple PDO Didacticiel.


Un tutoriel PDO simple et court


Q. La première question que je me posais était: qu'est-ce que «AOP»?

UNE. "PDO - Objets de données PHP - est une couche d'accès à la base de données fournissant une méthode uniforme d'accès à plusieurs bases de données. "

alt text


Connexion à MySQL

Avec mysql_* fonction ou nous pouvons le dire à l'ancienne (obsolète en PHP 5.5 et plus)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

Avec PDO: Tout ce que vous devez faire est de créer un nouveau PDO objet. Le constructeur accepte les paramètres pour spécifier la source de la base de données PDOle constructeur prend principalement quatre paramètres qui sont DSN (nom de la source de données) et éventuellement username, password.

Ici, je pense que vous connaissez tous, sauf DSN; c'est nouveau PDO. UNE DSN est essentiellement une chaîne d'options qui disent PDO le pilote à utiliser et les détails de connexion. Pour plus de référence, vérifiez PDO MySQL DSN.

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Remarque: vous pouvez aussi utiliser charset=UTF-8, mais parfois il provoque une erreur, il est donc préférable d'utiliser utf8.

S'il y a une erreur de connexion, il va lancer un PDOException objet qui peut être attrapé pour gérer Exception plus loin.

Bonne lecture: Connexions et gestion des connexions ¶ 

Vous pouvez également passer plusieurs options de pilote sous forme de tableau au quatrième paramètre. Je recommande de passer le paramètre qui met PDO en mode exception. Parce que certains PDO les pilotes ne prennent pas en charge les instructions préparées natives, donc PDO effectue l'émulation de la préparation. Cela vous permet également d'activer manuellement cette émulation. Pour utiliser les instructions préparées côté serveur natif, vous devez le définir explicitement false.

L'autre consiste à désactiver l'émulation de préparation qui est activée dans le MySQLpilote par défaut, mais préparer l'émulation doit être désactivé pour utiliser PDO sans encombre.

Je vais expliquer plus tard pourquoi préparer l'émulation doit être désactivée. Pour trouver une raison s'il vous plaît vérifier ce post.

Il est uniquement utilisable si vous utilisez une ancienne version de MySQL que je ne recommande pas.

Voici un exemple de comment vous pouvez le faire:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Pouvons-nous définir des attributs après la construction de PDO?

Oui, nous pouvons également définir certains attributs après la construction de PDO avec le setAttribute méthode:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

La gestion des erreurs


La gestion des erreurs est beaucoup plus facile PDO que mysql_*.

Une pratique courante lors de l'utilisation mysql_* est:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die() n'est pas un bon moyen de gérer l'erreur puisque nous ne pouvons pas gérer la chose dans die. Cela mettra fin au script de manière abrupte, puis répercutera l'erreur sur l'écran que vous ne souhaitez PAS montrer à vos utilisateurs finaux, et permettra aux pirates informatiques de découvrir votre schéma. Alternativement, les valeurs de retour de mysql_* fonctions peuvent souvent être utilisés en conjonction avec mysql_error () pour gérer les erreurs.

PDO offre une meilleure solution: les exceptions. Tout ce que nous faisons avec PDO devrait être enveloppé dans un try-catch bloc. Nous pouvons forcer PDO dans l'un des trois modes d'erreur en définissant l'attribut du mode d'erreur. Trois modes de gestion des erreurs sont ci-dessous.

  • PDO::ERRMODE_SILENT. Il est juste de définir des codes d'erreur et agit à peu près la même chose que mysql_* où vous devez vérifier chaque résultat, puis regardez $db->errorInfo(); pour obtenir les détails de l'erreur.
  • PDO::ERRMODE_WARNING Élever E_WARNING. (Avertissements d'exécution (erreurs non fatales) L'exécution du script n'est pas arrêtée.
  • PDO::ERRMODE_EXCEPTION: Lancer des exceptions. Cela représente une erreur soulevée par PDO. Vous ne devriez pas jeter un PDOException de votre propre code. Voir Des exceptions pour plus d'informations sur les exceptions en PHP. Cela ressemble beaucoup à or die(mysql_error());, quand il n'est pas attrapé. Mais contrairement à or die(), la PDOException peut être attrapé et manipulé avec élégance si vous choisissez de le faire.

Bonne lecture:

Comme:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Et vous pouvez l'envelopper dans try-catch, comme ci-dessous:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

Vous n'avez pas à gérer avec try-catch maintenant. Vous pouvez l'attraper à n'importe quel moment, mais je vous recommande fortement d'utiliser try-catch. Aussi, il peut être plus logique de l'attraper à l'extérieur de la fonction qui appelle le PDO des trucs:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

En outre, vous pouvez gérer par or die() ou on peut dire comme mysql_*, mais ce sera vraiment varié. Vous pouvez cacher les messages d'erreur dangereux dans la production en tournant display_errors off et juste en lisant votre journal des erreurs.

Maintenant, après avoir lu toutes les choses ci-dessus, vous pensez probablement: qu'est-ce que c'est que quand je veux juste commencer à me pencher SELECT, INSERT, UPDATE, ou DELETE déclarations? Ne vous inquiétez pas, nous y voilà:


Sélection de données

PDO select image

Alors qu'est-ce que vous faites dans mysql_* est:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Maintenant en PDO, vous pouvez le faire comme:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Ou

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

Remarque: Si vous utilisez la méthode comme ci-dessous (query()), cette méthode renvoie un PDOStatement objet. Donc, si vous voulez aller chercher le résultat, utilisez-le comme ci-dessus.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

Dans PDO Data, il est obtenu via le ->fetch(), une méthode de votre handle de déclaration. Avant d'appeler fetch, la meilleure approche consiste à indiquer à PDO comment vous souhaitez que les données soient récupérées. Dans la section ci-dessous, je l'explique.

Récupérer les modes

Notez l'utilisation de PDO::FETCH_ASSOC dans le fetch() et fetchAll() code ci-dessus. Cela dit PDO pour renvoyer les lignes en tant que tableau associatif avec les noms de champs en tant que clés. Il y a aussi beaucoup d'autres modes de récupération que je vais vous expliquer un par un.

Tout d'abord, j'explique comment sélectionner le mode d'extraction:

 $stmt->fetch(PDO::FETCH_ASSOC)

Dans ce qui précède, j'ai utilisé fetch(). Vous pouvez aussi utiliser:

Maintenant je viens chercher le mode:

  • PDO::FETCH_ASSOC: retourne un tableau indexé par le nom de la colonne tel qu'il est retourné dans votre jeu de résultats
  • PDO::FETCH_BOTH (valeur par défaut): retourne un tableau indexé par le nom de la colonne et le numéro de la colonne indexée 0 retournés dans votre jeu de résultats

Il y a encore plus de choix! Lisez à propos d'eux tous PDOStatement Récupère la documentation..

Obtenir le nombre de lignes:

À la place d'utiliser mysql_num_rows pour obtenir le nombre de lignes retournées, vous pouvez obtenir un PDOStatement et fait rowCount(), comme:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Obtenir le dernier identifiant inséré

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Insérer et mettre à jour ou supprimer des instructions

Insert and update PDO image

Ce que nous faisons en mysql_* la fonction est:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

Et en pdo, la même chose peut être faite par:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

Dans la requête ci-dessus PDO::exec exécute une instruction SQL et renvoie le nombre de lignes affectées.

Insérer et supprimer sera couvert plus tard.

La méthode ci-dessus n'est utile que lorsque vous n'utilisez pas de variable dans la requête. Mais quand vous avez besoin d'utiliser une variable dans une requête, ne jamais essayer comme ci-dessus et là pour instruction préparée ou déclaration paramétrée est.


Déclarations préparées

Q. Qu'est-ce qu'une déclaration préparée et pourquoi ai-je besoin d'eux?
UNE. Une instruction préparée est une instruction SQL précompilée qui peut être exécutée plusieurs fois en envoyant uniquement les données au serveur.

Le flux de travail type d'utilisation d'une instruction préparée est le suivant (cité de Wikipedia trois 3 points):

  1. PréparerLe modèle d'instruction est créé par l'application et envoyé au système de gestion de base de données (SGBD). Certaines valeurs ne sont pas spécifiées, appelées paramètres, espaces réservés ou variables de liaison ? au dessous de):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. Le SGBD analyse, compile et effectue l'optimisation des requêtes sur le modèle d'instruction et stocke le résultat sans l'exécuter.

  3. Exécuter: À un moment ultérieur, l'application fournit (ou lie) des valeurs pour les paramètres, et le SGBD exécute l'instruction (en renvoyant éventuellement un résultat). L'application peut exécuter l'instruction autant de fois qu'elle le souhaite avec des valeurs différentes. Dans cet exemple, il pourrait fournir «Pain» pour le premier paramètre et 1.00pour le deuxième paramètre.

Vous pouvez utiliser une instruction préparée en incluant des espaces réservés dans votre SQL. Il y a fondamentalement trois uns sans espace réservé (ne pas essayer ceci avec la variable ci-dessus), un avec des espaces réservés non nommés, et un avec des espaces réservés nommés.

Q. Alors maintenant, quels sont les espaces réservés nommés et comment puis-je les utiliser?
UNE. Emplacements réservés Utilisez des noms descriptifs précédés d'un deux-points au lieu de points d'interrogation. Nous ne nous soucions pas de la position / l'ordre de la valeur dans le nom du lieu titulaire:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Vous pouvez également lier à l'aide d'un tableau d'exécution:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Une autre fonctionnalité intéressante pour OOP amis est que les espaces réservés nommés ont la possibilité d'insérer des objets directement dans votre base de données, en supposant que les propriétés correspondent aux champs nommés. Par exemple:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

Q. Alors maintenant, quels sont les espaces réservés sans nom et comment puis-je les utiliser?
UNE. Prenons un exemple:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

et

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

Dans ce qui précède, vous pouvez voir les ? au lieu d'un nom comme dans un nom de lieu titulaire. Maintenant, dans le premier exemple, nous assignons des variables aux différents espaces réservés ($stmt->bindValue(1, $name, PDO::PARAM_STR);). Ensuite, nous assignons des valeurs à ces espaces réservés et exécutons l'instruction. Dans le second exemple, le premier élément du tableau va au premier ? et le second au second ?.

REMARQUE: Dans espaces réservés non nommés nous devons prendre soin du bon ordre des éléments dans le tableau que nous passons à la PDOStatement::execute() méthode.


SELECT, INSERT, UPDATE, DELETE requêtes préparées

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

REMARQUE:

toutefois PDO et / ou MySQLi ne sont pas complètement sûrs. Vérifiez la réponse Les instructions préparées par PDO sont-elles suffisantes pour empêcher l'injection SQL? par ircmaxell. Aussi, je cite une partie de sa réponse:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));

1160
2017-10-12 13:28



Commençons par le commentaire standard que nous donnons à tout le monde:

S'il vous plaît, n'utilisez pas mysql_* fonctions dans le nouveau code. Ils ne sont plus entretenus et sont officiellement obsolètes. Voir le boîte rouge? En savoir plus sur déclarations préparées à la place, et utiliser AOP ou MySQLi - Cet article vous aidera à décider lequel. Si vous choisissez PDO, Voici un bon tutoriel.

Passons par là, phrase par phrase, et expliquons:

  • Ils ne sont plus entretenus et sont officiellement désapprouvés

    Cela signifie que la communauté PHP abandonne progressivement le support de ces fonctions très anciennes. Ils sont susceptibles de ne pas exister dans une future version (récente) de PHP! L'utilisation continue de ces fonctions peut casser votre code dans le futur (pas si loin).

    NOUVEAU! - ext / mysql est maintenant officiellement obsolète depuis PHP 5.5!

    Plus récent! ext / mysql a été supprimé en PHP 7.

  • Au lieu de cela, vous devriez apprendre des déclarations préparées

    mysql_* l'extension ne supporte pas déclarations préparées, qui est (entre autres) une contre-mesure très efficace contre Injection SQL. Il a corrigé une vulnérabilité très sérieuse dans les applications dépendantes de MySQL, ce qui permet aux pirates d'accéder à votre script et d'effectuer toute requête possible sur votre base de données.

    Pour plus d'informations, voir Comment puis-je empêcher l'injection SQL en PHP?

  • Voir la boîte rouge?

    Quand vous allez à n'importe quel mysql page de manuel de fonction, vous voyez une boîte rouge, expliquant qu'il ne devrait plus être utilisé.

  • Utilisez soit PDO ou MySQLi

    Il existe de meilleures alternatives, plus robustes et bien construites, PDO - Objet de base de données PHP, qui offre une approche complète de la POO pour l'interaction avec les bases de données, et MySQLi, qui est une amélioration spécifique à MySQL.


279
2017-12-24 23:30



Facilité d'utilisation

Les raisons analytiques et synthétiques ont déjà été mentionnées. Pour les nouveaux arrivants, il y a une incitation plus importante à cesser d'utiliser les fonctions mysql_ datées.

Les API de base de données contemporaines sont juste Plus facile utiliser.

C'est surtout le paramètres liés ce qui peut simplifier le code. Et avec excellents tutoriels (comme vu ci-dessus) la transition vers AOP n'est pas trop ardu.

Réécrire une plus grande base de code à la fois prend cependant du temps. Raison d'être pour cette alternative intermédiaire:

Fonctions pdo_ * équivalentes à la place de mysql_ *

En utilisant <pdo_mysql.php> vous pouvez passer des anciennes fonctions mysql_ avec effort minimal. Cela ajoute pdo_ wrappers de fonction qui remplacent leur mysql_ homologues.

  1. Simplement include_once("pdo_mysql.php"); dans chaque script d'appel qui doit interagir avec la base de données.

  2. Retirer le mysql_ préfixe de fonction partout et le remplacer par pdo_.

    • mysql_connect() devient pdo_connect()
    • mysql_query() devient pdo_query()
    • mysql_num_rows() devient pdo_num_rows()
    • mysql_insert_id() devient pdo_insert_id()
    • mysql_fetch_array() devient pdo_fetch_array()
    • mysql_fetch_assoc() devient pdo_fetch_assoc()
    • mysql_real_escape_string() devient pdo_real_escape_string()
    • etc... 

       
  3. Votre code fonctionnera de la même manière et ressemblera toujours le même:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

Et voilà.
Votre code est en utilisant PDO.
Maintenant il est temps de réellement utiliser il.

Les paramètres liés peuvent être faciles à utiliser

Vous avez juste besoin d'une API moins lourde.

pdo_query() ajoute un support très facile pour les paramètres liés. La conversion de l'ancien code est simple:

Déplacez vos variables hors de la chaîne SQL.

  • Ajoutez-les en tant que paramètres de fonction délimités par des virgules pdo_query().
  • Placez des points d'interrogation ? comme des espaces réservés où les variables étaient avant.
  • Se débarrasser de ' guillemets simples qui ont précédemment inclus des valeurs de chaîne / variables.

L'avantage devient plus évident pour un code plus long.

Souvent, les variables de chaîne ne sont pas simplement interpolées dans SQL, mais concaténées avec des appels d'échappement entre les deux.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

Avec ? substituts appliqués vous n'avez pas à vous embêter avec ça:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Rappelez-vous que pdo_ * permet toujours soit ou.
N'échappe pas à une variable et lier dans la même requête.

  • La fonction d'espace réservé est fournie par le PDO réel derrière elle.
  • Ainsi également autorisé :named listes d'espaces réservés plus tard.

Plus important encore, vous pouvez passer les variables $ _REQUEST [] en toute sécurité derrière une requête. Lorsque soumis <form> les champs correspondent exactement à la structure de la base de données, c'est encore plus court:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

Tellement de simplicité. Mais revenons à quelques conseils de réécriture et des raisons techniques sur pourquoi vous pouvez vouloir vous débarrasser de mysql_et s'échapper.

Corriger ou supprimer n'importe quel oldschool sanitize() fonction

Une fois que vous avez tout converti mysql_ appels à pdo_query avec des paramètres liés, supprimez tous les redondants pdo_real_escape_string appels.

En particulier, vous devez corriger sanitize ou clean ou filterThis ou clean_data fonctions comme annoncé par des tutoriels datés sous une forme ou l'autre:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Le bug le plus flagrant est le manque de documentation. Plus important encore, l'ordre de filtrage était exactement dans le mauvais ordre.

  • La commande correcte aurait été: déconseillée stripslashes comme l'appel le plus profond, alors trim, ensuite strip_tags, htmlentities pour le contexte de sortie, et seulement enfin _escape_string comme son application devrait directement précéder l'inter-processus SQL.

  • Mais comme première étape juste débarasse-toi du _real_escape_string appel.

  • Vous devrez peut-être garder le reste de votre sanitize() Fonctionne pour l'instant si votre base de données et votre flux d'applications attendent des chaînes HTML-context-safe. Ajoutez un commentaire qu'il n'applique que du HTML qui s'échappe désormais.

  • La gestion de chaîne / valeur est déléguée à PDO et à ses instructions paramétrées.

  • S'il y avait une mention de stripslashes() dans votre fonction d'assainissement, cela peut indiquer une surveillance de niveau supérieur.

    Note historique sur magic_quotes. Cette fonctionnalité est à juste titre obsolète. Il est souvent incorrectement dépeint comme un échec Sécurité caractéristique cependant. Mais les magic_quotes sont autant une caractéristique de sécurité ratée que les balles de tennis ont échoué en tant que source de nutrition. Ce n'était simplement pas leur but.

    L'implémentation originale en PHP2 / FI l'a introduit explicitement avec juste "les guillemets seront automatiquement échappés, ce qui facilitera le passage direct des données de formulaire aux requêtes msql"Notamment, il était particulièrement sûr d'utiliser avec mSQL, comme cela ASCII pris en charge uniquement.
      Puis PHP3 / Zend a réintroduit magic_quotes pour MySQL et l'a mal documenté. Mais à l'origine, c'était juste un fonction de commodité, pas l'intention de la sécurité.

Comment les instructions préparées diffèrent

Lorsque vous brouillez des variables de chaîne dans les requêtes SQL, cela ne devient pas simplement plus compliqué à suivre. C'est aussi un effort supplémentaire pour MySQL de séparer à nouveau le code et les données.

Les injections SQL sont simplement quand données saigne dans le code le contexte. Un serveur de base de données ne peut pas repérer plus tard où PHP a collé à l'origine des variables entre des clauses de requête.

Avec les paramètres liés, vous séparez le code SQL et les valeurs de contexte SQL dans votre code PHP. Mais il ne se remet pas en arrière dans les coulisses (sauf avec PDO :: EMULATE_PREPARES). Votre base de données reçoit les commandes SQL non variées et les valeurs de variable 1: 1.

Alors que cette réponse souligne que vous devriez vous soucier des avantages de lisibilité de tomber mysql_. Il y a parfois aussi un avantage de performance (INSERTs répétés avec juste des valeurs différentes) en raison de cette séparation de données / code visible et technique.

Méfiez-vous que la liaison de paramètres n'est toujours pas une solution magique unique contre tout Injections SQL. Il gère l'utilisation la plus courante pour les données / valeurs. Mais vous ne pouvez pas mettre en liste blanche les identificateurs de noms de colonnes et de tables, aider à la construction de clauses dynamiques ou simplement créer des listes de valeurs de tableau.

Utilisation de PDO hybride

Celles-ci pdo_* Les fonctions wrapper créent une API stop-gap adaptée au codage. (C'est à peu près ce que MYSQLI aurait pu être si ce n'était pas pour le changement de signature de fonction idiosyncratique). Ils exposent également le vrai PDO la plupart du temps.
La réécriture ne doit pas s'arrêter à l'utilisation des nouveaux noms de fonctions pdo_. Vous pouvez passer un par un chaque pdo_query () dans un appel simple $ pdo-> prepare () -> execute ().

Il est toutefois préférable de recommencer à simplifier. Par exemple, le résultat commun aller chercher:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Peut être remplacé avec juste une itération foreach:

foreach ($result as $row) {

Ou mieux encore une récupération de tableau directe et complète:

$result->fetchAll();

Dans la plupart des cas, vous obtiendrez des avertissements plus utiles que ceux fournis par PDO ou mysql_ après des requêtes échouées.

Autres options

Donc, cela nous a permis de visualiser certains pratique raisons et une voie worthwile à déposer mysql_.

Juste passer à  ne le coupe pas tout à fait. pdo_query() est également juste une interface sur elle.

À moins que vous n'introduisiez une liaison de paramètre ou que vous utilisiez autre chose de l'API plus agréable, c'est un changement inutile. J'espère que c'est assez simple pour ne pas décourager les nouveaux arrivants. (L'éducation fonctionne généralement mieux que la prohibition.)

Bien qu'il se qualifie pour la catégorie la plus simple-chose-qui-pourrait-éventuellement-travailler, c'est aussi un code très expérimental. Je viens de l'écrire au cours du week-end. Il y a cependant une pléthore d'alternatives. Juste google pour Abstraction de base de données PHP et parcourir un peu. Il y a toujours eu et il y aura beaucoup d'excellentes bibliothèques pour de telles tâches.

Si vous souhaitez simplifier davantage votre interaction avec la base de données, les mappeurs Paris / Idiorm valent la peine d'essayer. Tout comme personne n'utilise le DOM trivial dans JavaScript, vous n'avez plus besoin de garder une interface de base de données brute de nos jours.


200
2017-10-12 13:22



le mysql_ les fonctions:

  1. sont périmés - ils ne sont plus maintenus
  2. ne vous permet pas de passer facilement à un autre backend de base de données
  3. ne supporte pas les instructions préparées, d'où
  4. encourager les programmeurs à utiliser la concaténation pour créer des requêtes, ce qui entraîne des vulnérabilités d'injection SQL

134
2018-01-01 17:42



En parlant de technique raisons, il y en a seulement quelques-unes, extrêmement spécifiques et rarement utilisées. Très probablement, vous ne les utiliserez jamais dans votre vie.
Peut-être que je suis trop ignorant, mais je n'ai jamais eu l'occasion de les utiliser

  • requêtes non bloquantes et asynchrones
  • procédures stockées renvoyant plusieurs jeux de résultats
  • Chiffrement (SSL)
  • Compression

Si vous avez besoin d'eux - ce sont sans doute des raisons techniques pour passer de l'extension mysql à quelque chose de plus élégant et moderne.

Néanmoins, il existe également des problèmes non techniques qui peuvent rendre votre expérience un peu plus difficile.

  • Une utilisation plus poussée de ces fonctions avec les versions modernes de PHP soulèvera des notifications au niveau obsolète. Ils peuvent simplement être désactivés.
  • dans un avenir lointain, ils peuvent être éventuellement supprimés de la version PHP par défaut. Pas grave non plus, puisque mydsql ext sera déplacé dans PECL et chaque hébergeur sera heureux de compiler PHP avec lui, car ils ne veulent pas perdre des clients dont les sites fonctionnaient depuis des décennies.
  • forte résistance de la communauté Stackoverflow. Chaque fois que vous mentionnez ces fonctions honnêtes, on vous dit qu'elles sont sous un tabou strict.
  • En tant qu'utilisateur PHP moyen, votre idée d'utiliser ces fonctions est probablement sujette aux erreurs et incorrecte. Juste à cause de tous ces nombreux tutoriels et manuels qui vous enseignent le mauvais chemin. Pas les fonctions elles-mêmes - je dois le souligner - mais la façon dont elles sont utilisées.

Ce dernier problème est un problème.
Mais, à mon avis, la solution proposée n'est pas meilleure non plus.
Il me semble trop idéaliste un rêve que tous ces utilisateurs PHP apprendront à gérer correctement les requêtes SQL à la fois. Très probablement, ils changeraient simplement mysql_ * en mysqli_ * mécaniquement, laissant l'approche la même. Surtout parce que mysqli fait des déclarations préparées incroyablement douloureuses et gênantes.
Sans oublier que originaire de déclarations préparées ne suffisent pas à protéger à partir des injections SQL, et ni mysqli ni PDO ne proposent de solution.

Ainsi, au lieu de combattre cette extension honnête, je préférerais combattre de mauvaises pratiques et éduquer les gens de la bonne façon.

En outre, il existe des raisons fausses ou non significatives, comme

  • Ne supporte pas les procédures stockées (nous utilisions mysql_query("CALL my_proc"); des lustres)
  • Ne supporte pas les transactions (comme ci-dessus)
  • Ne prend pas en charge les instructions multiples (qui en a besoin?)
  • Pas en développement actif (alors quoi? toi de quelque manière pratique?)
  • Manque une interface OO (pour en créer une est une affaire de plusieurs heures)
  • Ne prend pas en charge les instructions préparées ou les requêtes paramétrées

Le dernier est un point intéressant. Bien que mysql ext ne supporte pas originaire de déclarations préparées, ils ne sont pas nécessaires pour la sécurité. Nous pouvons facilement truquer des instructions préparées en utilisant des espaces réservés gérés manuellement (comme le fait PDO):

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

voila, tout est paramétré et sûr.

Mais d'accord, si vous n'aimez pas la boîte rouge dans le manuel, un problème de choix se pose: mysqli ou PDO?

Eh bien, la réponse serait la suivante:

  • Si vous comprenez la nécessité d'utiliser un couche d'abstraction de base de données et à la recherche d'une API pour en créer une, mysqli est un très bon choix, car il supporte en effet de nombreuses fonctionnalités spécifiques à mysql.
  • Si, comme la grande majorité des gens de PHP, vous utilisez des appels d'API brutes directement dans le code de l'application (ce qui est essentiellement faux pratique) - PDO est le seul choix, car cette extension prétend être non seulement une API mais plutôt une semi-DAL, encore incomplète mais offrant de nombreuses fonctionnalités importantes, dont deux font de PDO un distingué de mysqli:

    • contrairement à mysqli, PDO peut lier des espaces réservés en valeur, ce qui rend les requêtes construites dynamiquement réalisables sans plusieurs écrans de code assez désordonné.
    • contrairement à mysqli, PDO peut toujours retourner le résultat de la requête dans un simple tableau habituel, alors que mysqli ne peut le faire que sur les installations mysqlnd.

Donc, si vous êtes un utilisateur moyen de PHP et que vous voulez vous épargner une tonne de maux de tête en utilisant des instructions préparées natives, PDO - encore - est le seul choix.
Cependant, PDO n'est pas une balle d'argent aussi et a ses difficultés.
Donc, j'ai écrit des solutions pour tous les pièges courants et les cas complexes dans le Wiki tag AOP

Néanmoins, tout le monde parle d'extensions qui manquent toujours le 2 faits importants à propos de Mysqli et AOP:

  1. Affirmation préparée n'est pas une balle d'argent. Il existe des identifiants dynamiques qui ne peuvent pas être liés à l'aide d'instructions préparées. Il existe des requêtes dynamiques avec un nombre inconnu de paramètres qui rendent la création de requêtes une tâche difficile.

  2. Ni mysqli_ * ni les fonctions PDO ne devraient apparaître dans le code de l'application.
    Il devrait y avoir un couche d'abstraction entre eux et le code de l'application, qui fera tout le sale boulot de liaison, de bouclage, de gestion des erreurs, etc. à l'intérieur, rendant le code de l'application DRY et propre. Surtout pour les cas complexes comme la construction de requêtes dynamiques.

Donc, passer à PDO ou mysqli ne suffit pas. On doit utiliser un ORM, ou un constructeur de requête, ou n'importe quelle classe d'abstraction de base de données au lieu d'appeler des fonctions d'API brutes dans leur code.
Et contrairement - si vous avez une couche d'abstraction entre votre code d'application et l'API mysql - le moteur utilisé n'a pas vraiment d'importance. Vous pouvez utiliser mysql ext jusqu'à ce qu'il soit déprécié, puis réécrire facilement votre classe d'abstraction à un autre moteur, avoir tout le code de l'application intact.

Voici quelques exemples basés sur mon classe safemysql pour montrer comment une telle classe d'abstraction devrait être:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

Comparez cette seule ligne avec quantité de code dont vous aurez besoin avec PDO.
Puis comparez avec quantité de code fou vous aurez besoin d'instructions préparées par Mysqli. Notez que la gestion des erreurs, le profilage, la journalisation des requêtes sont déjà intégrés et en cours d'exécution.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

Comparez-le avec les insertions PDO habituelles, lorsque chaque nom de champ est répété six à dix fois - dans tous ces nombreux espaces réservés nommés, liaisons et définitions de requêtes.

Un autre exemple:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

Vous pouvez difficilement trouver un exemple pour PDO pour gérer un tel cas pratique.
Et ce sera trop verbeux et très probablement dangereux.

Donc, une fois de plus - ce n'est pas seulement le pilote brut qui devrait vous intéresser, mais la classe d'abstraction, utile non seulement pour les exemples stupides du manuel du débutant, mais pour résoudre tous les problèmes de la vie réelle.


98
2017-10-12 13:23



Il y a plusieurs raisons, mais peut-être la plus importante est-elle que ces fonctions encouragent les pratiques de programmation non sécurisées parce qu'elles ne prennent pas en charge les déclarations préparées. Les instructions préparées aident à prévenir les attaques par injection SQL.

En utilisant mysql_* fonctions, vous devez vous rappeler d'exécuter les paramètres fournis par l'utilisateur via mysql_real_escape_string(). Si vous oubliez dans un seul endroit ou si vous n'échappez qu'une partie de l'entrée, votre base de données peut être attaquée.

Utilisation d'instructions préparées dans PDO ou mysqli fera en sorte que ces sortes d'erreurs de programmation sont plus difficiles à faire.


87
2017-10-12 13:24



Parce que (entre autres raisons) il est beaucoup plus difficile de s'assurer que les données d'entrée sont aseptisées. Si vous utilisez des requêtes paramétrées, comme avec PDO ou mysqli, vous pouvez entièrement éviter le risque.

A titre d'exemple, quelqu'un pourrait utiliser "enhzflep); drop table users" en tant que nom d'utilisateur Les anciennes fonctions permettront d'exécuter plusieurs instructions par requête, donc quelque chose comme ce méchant bugger peut supprimer une table entière.

Si l'on utilisait PDO de mysqli, le nom d'utilisateur finirait par être "enhzflep); drop table users".

Voir bobby-tables.com.


70
2017-09-18 12:28