Guide de démarrage rapide
Ce guide de démarrage rapide va vous aider à vous familiariser
avec l'API PHP MySQL.
Il fournit une vue d'ensemble de l'extension mysqli. Des exemples de code
sont fournis pour tous les aspects importants de l'API. Les concepts
de base de données sont expliqués avec une finesse permettant de
comprendre les spécificités des concepts de MySQL.
Requis : Vous devez être familier avec le langage de programmation PHP,
le langage SQL ainsi qu'avoir quelques bases avec le serveur MySQL.
Interface procédurale et orientée objet
L'extension mysqli fournit 2 interfaces. Elle supporte la programmation
procédurale mais aussi, la programmation orientée objet.
Les utilisateurs migrants depuis l'ancienne extension mysql préfèreront
l'interface procédurale. Cette interface est similaire à celle utilisée
par l'ancienne extension mysql. Dans la plupart des cas, les noms de fonctions
ne diffèrent que par leurs préfixes. Quelques fonctions mysqli prennent
un gestionnaire de connexion comme premier argument, alors que la fonction
correspondante de l'ancienne interface mysql le prenait comme argument
optionnel en dernière position.
Migration facile depuis l'ancienne extension mysql
&example.outputs;
L'interface orientée objet
En plus de l'interface procédurale, les utilisateurs peuvent choisir
d'utiliser l'interface orientée objet. La documentation est organisée
en utilisant cette interface. Elle montre les fonctions groupées
par leurs buts, rendant simple le démarrage de la programmation.
La section référence fournit des exemples sur les deux syntaxes.
Il n'y a pas de différence significative d'un point de vue performance
entre les deux interfaces. Les utilisateurs peuvent faire leur choix
que d'un point de vue personnel.
Interface procédurale et orientée objet
query("SELECT 'choices to please everybody.' AS _msg FROM DUAL");
$row = $result->fetch_assoc();
echo $row['_msg'];
]]>
&example.outputs;
L'interface orientée objet est utilisée dans le démarrage rapide de la documentation
en raison du fait que la section référence est organisée de cette façon.
Mixage des styles
Il est possible de passer d'un style à un autre à tout moment bien que ce ne
soit pas recommandé pour des raisons de clarté et de style de codage.
Mauvais style de codage
fetch_assoc()) {
echo $row['_msg'];
}
]]>
&example.outputs;
Voir aussimysqli::__constructmysqli::querymysqli_result::fetch_assoc$mysqli::connect_errno$mysqli::connect_error$mysqli::errno$mysqli::errorLe résumé des fonctions de l'extension MySQLiConnexions
Le serveur MySQL supporte l'utilisation de différentes couches de transport
pour les connexions. Les connexions peuvent utiliser TCP/IP, les sockets
de domaine Unix ou les pipes nommés Windows.
Le nom d'hôte localhost a une signification particulière.
Il est lié à l'utilisation des sockets de domaine Unix.
Pour ouvrir une connexion TCP/IP sur l'hôte local, 127.0.0.1 doit être utilisé
au lieu de localhost.
Signification spéciale de localhost
host_info . "\n";
$mysqli = new mysqli("127.0.0.1", "user", "password", "database", 3306);
echo $mysqli->host_info . "\n";
]]>
&example.outputs;
Paramètres par défaut d'une connexion
Suivant la fonction de connexion utilisée, des paramètres peuvent être omis.
Si un paramètre n'est pas fourni, alors l'extension tentera d'utiliser les valeurs
par défaut définies dans le fichier de configuration de PHP.
Paramètres par défaut
Ces valeurs de paramètres sont alors passées à la bibliothèque cliente
utilisée par l'extension. Si la bibliothèque cliente détecte un paramètre
vide ou non défini, alors elle utilisera les valeurs par défaut internes à
la bibliothèque.
Valeurs par défaut internes à la bibliothèque pour la connexion
Si la valeur de l'hôte n'est pas définie ou est vide, alors la bibliothèque cliente
utilisera par défaut une connexion de type socket Unix sur localhost.
Si le socket n'est pas défini ou vide, et qu'une connexion de type socket Unix est
demandée, alors une connexion au socket par défaut /tmp/mysql.sock
sera tentée.
Sous les systèmes Windows, le nom d'hôte . est interprété
par la bibliothèque cliente comme une tentative d'ouvrir un tube nommé Windows
pour la connexion. Dans ce cas, le paramètre socket est interprété comme
un tube nommé. S'il n'est pas fourni ou vide, alors le socket (tube nommé)
vaudra par défaut \\.\pipe\MySQL.
Si ni un socket de domaine Unix, ni un tube nommé Windows n'est fourni, une connexion
de base sera établie et si la valeur du port n'est pas défini, la bibliothèque
utilisera le port 3306.
La bibliothèque mysqlnd et la bibliothèque
cliente MySQL (libmysqlclient) implémentent la même logique pour déterminer les valeurs
par défaut.
Options de connexion
Des options de connexion sont disponibles pour, par exemple, définir
des commandes d'initialisation à exécuter lors de la connexion, ou
pour demander l'utilisation d'un jeu de caractères particulier. Les options
de connexion doivent être définies avant la connexion au réseau.
Pour définir une option de connexion, l'opération de connexion doit
être effectuée en 3 étapes : création d'un gestionnaire de connexion avec
mysqli_init ou mysqli::__construct,
définition des options demandées en utilisant
mysqli::options, et connexion au réseau avec
mysqli::real_connect.
File d'attente de connexion
L'extension mysqli supporte les connexions persistantes au base de données,
qui sont des connexions spéciales. Par défaut, chaque connexion à une
base de données ouverte par un script est soit explicitement close par
l'utilisateur durant l'exécution, ou soit libérée automatiquement à la fin
du script. Ce n'est pas le cas d'une connexion persistante. En effet,
elle sera placée dans une file d'attente pour une ré-utilisation future,
si une connexion au même serveur, utilisant le même nom d'utilisateur, le
même mot de passe, le même socket, le même port, ainsi que la même base de données
est ouverte. Cette ré-utilisation permet d'alléger la charge indue par les
connexions.
Chaque processus PHP utilise sa propre file d'attente de connexions mysqli.
Suivant le modèle de déploiement du serveur web, un processus PHP peut
servir une ou plusieurs requêtes. Toutefois, une connexion mise en file d'attente
peut être utilisée par un ou plusieurs scripts par la suite.
Les connexions persistantes
Si une connexion persistante pour une combinaison d'hôte, de nom d'utilisateur,
de mot de passe, de socket, de port et de base de données inutilisée ne peut
être trouvée dans la file d'attente de connexion, alors mysqli ouvrira une nouvelle
connexion. L'utilisation des connexions persistantes peut être activée ou désactivée
en utilisant la directive PHP
mysqli.allow_persistent.
Le nombre total de connexions ouvertes par un script peut être limité avec
la directive mysqli.max_links.
Le nombre maximal de connexions persistantes par processus PHP peut être
restreint avec la directive mysqli.max_persistent.
Veuillez noter que le serveur web peut engendrer plusieurs processus PHP.
Une plainte courante contre les connexions persistantes est que leurs
statuts n'est pas ré-initialisés avant la ré-utilisation. Par exemple,
les transactions ouvertes et non terminées ne sont pas automatiquement
annulées. Mais aussi, les modifications autorisées survenant entre le moment
où la connexion est mise en file d'attente et sa ré-utilisation ne seront
pas prises en compte. Ce comportement peut être vu comme un effet de
bord non désiré. Au contraire, le nom persistent
peut être compris comme une promesse sur le fait que le statut persiste
réellement.
L'extension mysqli supporte deux interprétations d'une connexion persistante :
statut persistant, et un statut réinitialisé avant ré-utilisation. Par
défaut, il sera réinitialisé. Avant qu'une connexion persistante ne soit
réutilisée, l'extension mysqli appelle implicitement la fonction
mysqli::change_user pour réinitialiser le statut.
La connexion persistante apparaît à l'utilisateur comme si elle venait
juste d'être ouverte. Aucune trace d'une utilisation précédente ne
sera visible.
La fonction mysqli::change_user est une opération couteuse.
Pour de meilleures performances, les utilisateurs peuvent vouloir re-compiler
l'extension avec le drapeau de compilation MYSQLI_NO_CHANGE_USER_ON_PCONNECT.
Ainsi, il sera laissé à l'utilisateur le choix entre un comportement sécurisé
et une performance optimisée. Les deux ont comme but l'optimisation. Pour
une utilisation plus simple, le comportement sécurisé a été placé
par défaut au détriment d'une performance maximale.
Voir aussimysqli_initmysqli::__constructmysqli::optionsmysqli::real_connectmysqli::change_user$mysqli::host_infoLes options de configuration MySQLiLes connexions persistantes aux bases de donnéesExécution des requêtes
Les requêtes peuvent être exécutées avec les fonctions
mysqli::query, mysqli::real_query
et mysqli::multi_query.. La fonction
mysqli::query est la plus commune, et combine
l'exécution de la requête avec une récupération
de son jeu de résultats en mémoire tampon, s'il y en a un, en un seul appel.
Appeler la fonction mysqli::query
est identique à appeler la fonction
mysqli::real_querysuivie d'un appel à la fonction
mysqli::store_result.
Exécution des requêtes
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
]]>
Jeux de résultats en mémoire tampon
Après exécution de la requête, les résultats peuvent être intégralement récupérés
en une fois ou bien être lus ligne par ligne. La mise en mémoire
tampon du jeu de résultats côté client autorise le serveur à libérer
les ressources associées avec le résultat de la requête aussi vite que possible.
De manière générale, les clients sont lents à parcourir les jeux de résultats.
Toutefois, il est recommandéd'utiliser la mise en mémoire tampon des
jeux de résultats. La fonction mysqli::query
combine à la fois l'exécution de la requête et la mise en mémoire tampon du jeu de résultats.
Les applications PHP peuvent naviguer librement dans les résultats
mis en mémoire tampon. La navigation est rapide car les jeux de résultats
sont stockés dans la mémoire client. Veuillez garder à l'esprit qu'il
est souvent plus simple de réaliser cette opération par le client que
par le serveur.
Navigation dans des résultats mis en mémoire tampon
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$result = $mysqli->query("SELECT id FROM test ORDER BY id ASC");
echo "Ordre inversé...\n";
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
$row = $result->fetch_assoc();
echo " id = " . $row['id'] . "\n";
}
echo "Ordre du jeu de résultats...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
&example.outputs;
Jeux de résultats non mis en mémoire tampon
Si la mémoire client est une ressource limitée, et que la libération
des ressources serveur aussi vite que possible pour conserver une charge
serveur basse n'est pas nécessaire, les résultats non mis en mémoire tampon
peuvent être utilisés. La navigation au travers de résultats non mis en mémoire
tampon n'est pas possible tant que toutes les lignes n'ont pas été lues.
Navigation dans des résultats non mis en mémoire tampon
real_query("SELECT id FROM test ORDER BY id ASC");
$result = $mysqli->use_result();
echo "Ordre du jeu de résultats...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
Types de données des valeurs du jeu de résultats
Les fonctions mysqli::query, mysqli::real_query
et mysqli::multi_query sont utilisées pour exécuter des
requêtes non-préparées. Au niveau du protocole client-serveur MySQL, la commande
COM_QUERY ainsi que le protocole texte sont utilisés pour
l'exécution de la requête. Avec le protocole texte, le serveur MySQL convertit
toutes les données d'un jeu de résultats en chaînes de caractères avant de les envoyer.
La bibliothèque cliente mysql reçoit toutes les valeurs des colonnes sous forme
de chaîne de caractères. Aucun autre transtypage côté client n'est effectué
pour retrouver le type natif des colonnes. A la place de cela, toutes les valeurs sont
fournis sous la forme de chaîne de caractères PHP.
Le protocole texte retourne des chaînes de caractères par défaut
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
&example.outputs;
Il est possible de convertir des colonnes de type entières et nombres à virgule flottante
en nombre PHP en définissant l'option de connexion
MYSQLI_OPT_INT_AND_FLOAT_NATIVE, si vous utilisez la bibliothèque
mysqlnd. Si défini, la bibliothèque mysqlnd va vérifier les méta-données des types
des colonnes dans le jeu de résultats et va convertir les colonnes SQL numériques
en nombres PHP, si la valeur entre dans l'intervalle autorisé de PHP.
De cette façon, par exemple, les colonnes SQL INT sont retournées sous la forme
d'entier.
Types natifs des données avec mysqlnd et l'option de connexion
options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1);
$mysqli->real_connect("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
&example.outputs;
Voir aussimysqli::__constructmysqli::optionsmysqli::real_connectmysqli::querymysqli::multi_querymysqli::use_resultmysqli::store_resultLes requêtes préparées
La base de données MySQL supporte les requêtes préparées. Une requête
préparée ou requête paramétrable est utilisée pour exécuter la
même requête plusieurs fois, avec une grande efficacité et protège
des injections SQL.
Flux de travail de base
L'exécution d'une requête préparée se déroule en deux étapes :
la préparation et l'exécution. Lors de la préparation, un template
de requête est envoyé au serveur de base de données. Le serveur effectue
une vérification de la syntaxe, et initialise les ressources internes
du serveur pour une utilisation ultérieure.
Le serveur MySQL supporte le mode anonyme, avec des marqueurs de position
utilisant le caractère ?.
La préparation est suivie de l'exécution. Pendant l'exécution, le client lie
les valeurs des paramètres et les envoie au serveur. Le serveur exécute
l'instruction avec les valeurs liées en utilisant les ressources internes
précédemment créées.
Première étape : la préparation
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
// Requête préparée, étape 1 : préparation
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
// Requête préparée, étape 2 : lie les valeurs et exécute la requête
$id = 1;
$label = 'PHP';
$stmt->bind_param("is", $id, $label); // "is" means that $id is bound as an integer and $label as a string
$stmt->execute();
]]>
Exécution répétée
Une requête préparée peut être exécutée à plusieurs reprises. A chaque
exécution, la valeur courante de la variable liée est évaluée, et envoyée
au serveur. La requête n'est pas analysée de nouveau. Le template de requête
n'est pas une nouvelle fois envoyée au serveur non plus.
Requête de type INSERT préparée une seule fois, et exécutée plusieurs fois
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
// Requête préparée, étape 1 : la préparation
if (!($stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?)"))) {
echo "Échec lors de la préparation : (" . $mysqli->errno . ") " . $mysqli->error;
}
// Requête préparée, étape 2 : lie les valeurs et exécute la requête
$id = 1;
$stmt->bind_param("is", $id, $label); // "is" means that $id is bound as an integer and $label as a string
$data = [
1 => 'PHP',
2 => 'Java',
3 => 'C++'
];
foreach ($data as $id => $label) {
$stmt->execute();
}
$result = $mysqli->query('SELECT id, label FROM test');
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
&example.outputs;
string(1) "1"
["label"]=>
string(3) "PHP"
}
[1]=>
array(2) {
["id"]=>
string(1) "2"
["label"]=>
string(4) "Java"
}
[2]=>
array(2) {
["id"]=>
string(1) "3"
["label"]=>
string(3) "C++"
}
}
]]>
Chaque requête préparée occupe des ressources sur le serveur. Elles doivent
être fermées explicitement immédiatement après utilisation. Si vous ne
le faîtes pas, la requête sera fermée lorsque le gestionnaire de requête
sera libéré par PHP.
L'utilisation de requête préparée n'est pas toujours la façon la plus
efficace d'exécuter une requête. Une requête préparée exécutée une seule
fois provoque plus d'aller-retour client-serveur qu'une requête non préparée.
C'est pour cela que la requête de type SELECT
n'est pas exécutée comme requête préparée dans l'exemple ci-dessus.
De plus, vous devez prendre en considération l'utilisation des syntaxes
multi-INSERT MySQL pour les INSERTs. Par exemple, les multi-INSERTs requièrent
moins d'aller-retour client-serveur que la requête préparée vue dans l'exemple ci-dessus.
Moins d'aller-retour en utilisant les multi-INSERTs SQL
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$values = [1, 2, 3, 4];
$stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?), (?), (?), (?)");
$stmt->bind_param('iiii', ...$values);
$stmt->execute();
]]>
Types de données des valeurs du jeu de résultats
Le protocole serveur client MySQL définit un protocole de transfert des données
différent pour les requêtes préparées et pour les requêtes non préparées.
Les requêtes préparées utilisent un protocole appelé binaire. Le serveur MySQL
envoie les données du jeu de résultats "tel que", au format binaire. Les résultats
ne sont pas sérialisés en chaînes de caractères avant envoi. La bibliothèque cliente
reçoit des données binaires et tente de convertir les valeurs en un type de données
PHP approprié. Par exemple, les résultats depuis une colonne INT
SQL seront fournis comme variables de type entier PHP.
Types de données natifs
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
&example.outputs;
Ce comportement diffère pour les requêtes non préparées. Par défaut, les
requêtes non préparées retournent tous les résultats sous forme de chaînes
de caractères. Ce comportement par défaut peut être modifié en utilisant
une option lors de la connexion. Si cette option est utilisée,
alors il n'y aura plus de différence entre une requête préparée et une
requête non préparée.
Récupération des résultats en utilisant des variables liées
Les résultats depuis les requêtes préparées peuvent être récupérées
en liant les variables de sortie, ou en interrogeant l'objet
mysqli_result.
Les variables de sortie doivent être liées après l'exécution de la requête.
Une variable doit être liée pour chaque colonne du jeu de résultats de la requête.
Liage des variables de sortie
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$stmt->bind_result($out_id, $out_label);
$out_id = NULL;
$out_label = NULL;
if (!$stmt->bind_result($out_id, $out_label)) {
echo "Échec lors du liage des paramètres de sortie : (" . $stmt->errno . ") " . $stmt->error;
}
while ($stmt->fetch()) {
printf("id = %s (%s), label = %s (%s)\n", $out_id, gettype($out_id), $out_label, gettype($out_label));
}
]]>
&example.outputs;
Les requêtes préparées retournent des jeux de résultats non mis en mémoire tampon
par défaut. Les résultats de la requête ne sont pas implicitement récupérés
et transférés depuis le serveur vers le client pour une mise en mémoire tampon
côté client. Le jeu de résultats prend des ressources serveur tant que tous
les résultats n'ont pas été récupérés par le client. Aussi, il est recommandé
de les récupérer rapidement. Si un client échoue dans la récupération de
tous les résultats, ou si le client ferme la requête avant d'avoir récupéré
toutes les données, les données doivent être récupérées implicitement par
mysqli.
Il est également possible de mettre en mémoire tampon les résultats d'une
requête préparée en utilisant la fonction
mysqli_stmt::store_result.
Récupération des résultats en utilisant l'interface mysqli_result
Au lieu d'utiliser des résultats liés, les résultats peuvent aussi être récupérées
via l'interface mysqli_result. La fonction mysqli_stmt::get_result
retourne un jeu de résultats mis en mémoire tampon.
Utilisation de mysqli_result pour récupérer les résultats
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
&example.outputs;
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
}
]]>
L'utilisation de l'interface mysqli_result offre d'autres avantages
d'un point de vue flexibilité dans la navigation dans le jeu de résultats côté client.
Jeu de résultats mis en mémoire tampon pour plus de flexibilité dans la lecture
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP'), (2, 'Java'), (3, 'C++')");
$stmt = $mysqli->prepare("SELECT id, label FROM test");
$stmt->execute();
$result = $stmt->get_result();
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
var_dump($result->fetch_assoc());
}
]]>
&example.outputs;
int(3)
["label"]=>
string(1) "C++"
}
array(2) {
["id"]=>
int(2)
["label"]=>
string(1) "Java"
}
array(2) {
["id"]=>
int(1)
["label"]=>
string(1) "PHP"
}
]]>
Échappement et injection SQL
Les variables liées sont envoyées au serveur séparément de la requête,
ne pouvant ainsi pas interférer avec celle-ci. Le serveur utilise ces valeurs
directement au moment de l'exécution, après que le template ne soit
analysé. Les paramètres liés n'ont pas besoin d'être échappés sachant
qu'ils ne sont jamais placés dans la chaîne de requête directement.
Une astuce doit être fournie au serveur pour spécifier le type de
variable liée, afin d'effectuer la conversion appropriée. Voir la
fonction mysqli_stmt::bind_param pour plus d'informations.
Une telle séparation est souvent considérée comme la seule fonctionnalité
pour se protéger des injections SQL, mais le même degré de sécurité peut
être atteint avec les requêtes non-préparées, si toutes les valeurs sont
correctement formatées. Notez qu'un formattage correct n'est pas la même
chose qu'un échappement et nécessite plus de logique qu'un simple échappement.
Aussi, les requêtes préparées sont simplement une méthode plus simple
et moins prompte aux erreurs concernant cette approche sécuritaire.
Émulation côté client de la préparation d'une requête
L'API n'inclut pas d'émulation côté client de la préparation d'une requête.
Voir aussimysqli::__constructmysqli::querymysqli::preparemysqli_stmt::preparemysqli_stmt::executemysqli_stmt::bind_parammysqli_stmt::bind_resultLes procédures stockées
La base de données MySQL supporte les procédures stockées. Une procédure stockée
est une sous routine stockée dans le catalogue de la base de données. Les
applications peuvent appeler et exécuter une procédure stockée. La
requête SQL CALL est utilisée pour exécuter
une procédure stockée.
Paramètre
Les procédures stockées peuvent avoir des paramètres IN,
INOUT and OUT, suivant la version de MySQL.
L'interface mysqli n'a pas de notion spécifique des différents types de paramètres.
Paramètre IN
Les paramètres d'entrée sont fournis avec la requête CALL.
Assurez-vous d'échapper correctement les valeurs.
Appel d'une procédure stockée
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;");
$mysqli->query("CALL p(1)");
$result = $mysqli->query("SELECT id FROM test");
var_dump($result->fetch_assoc());
]]>
&example.outputs;
string(1) "1"
}
]]>
Paramètre INOUT/OUT
Les valeurs des paramètres INOUT/OUT
sont accédées en utilisant les variables de session.
Utilisation des variables de session
query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p(OUT msg VARCHAR(50)) BEGIN SELECT "Hi!" INTO msg; END;');
$mysqli->query("SET @msg = ''");
$mysqli->query("CALL p(@msg)");
$result = $mysqli->query("SELECT @msg as _p_out");
$row = $result->fetch_assoc();
echo $row['_p_out'];
]]>
&example.outputs;
Les développeurs d'application et de framework peuvent fournir une API
plus conviviale utilisant un mix des variables de session et une inspection
du catalogue de la base de données. Cependant, veuillez garder à l'esprit
l'impact sur les performances dû à une solution personnalisée basée
sur l'inspection du catalogue.
Gestion des jeux de résultats
Les procédures stockées peuvent retourner des jeux de résultats. Les jeux de
résultats retournés depuis une procédure stockée ne peuvent être récupérés
correctement en utilisant la fonction mysqli::query.
La fonction mysqli::query combine l'exécution de la requête
et la récupération du premier jeu de résultats dans un jeu de résultats mis en
mémoire tampon, s'il y en a. Cependant, il existe d'autres jeux de résultats
issus de la procédure stockée qui sont cachés de l'utilisateur et qui
font que la fonction mysqli::query échoue lors de la
récupération des jeux de résultats attendus de l'utilisateur.
Les jeux de résultats retournés depuis une procédure stockée sont
récupérés en utilisant la fonction mysqli::real_query
ou mysqli::multi_query.
Ces deux fonctions autorisent la récupération de n'importe quel nombre
de jeux de résultats retournés par une requête, comme la requête
CALL. L'échec dans la récupération de tous les jeux de résultats
retournés par une procédure stockée cause une erreur.
Récupération des résultats issus d'une procédure stockée
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$mysqli->multi_query("CALL p()");
do {
if ($res = $mysqli->store_result()) {
var_dump($result->fetch_all());
$result->free();
}
} while ($mysqli->next_result());
]]>
&example.outputs;
array(1) {
[0]=>
string(1) "1"
}
[1]=>
array(1) {
[0]=>
string(1) "2"
}
[2]=>
array(1) {
[0]=>
string(1) "3"
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "2"
}
[1]=>
array(1) {
[0]=>
string(1) "3"
}
[2]=>
array(1) {
[0]=>
string(1) "4"
}
}
]]>
Utilisation des requêtes préparées
Aucune gestion spéciale n'est requise lors de l'utilisation de l'interface
de préparation des requêtes pour récupérer les résultats depuis la même procédure
stockée que celle ci-dessous. Les interfaces de requête préparée et non préparée
sont similaires. Veuillez noter que toutes les versions du serveur MySQL ne
supporte pas la préparation des requêtes SQL CALL.
Procédures stockées et requête préparée
connect_errno) {
echo "Échec lors de la connexion à MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
if (!$stmt->execute()) {
echo "Échec lors de l'exécution : (" . $stmt->errno . ") " . $stmt->error;
}
do {
if ($result = $stmt->get_result()) {
var_dump($result->fetch_all());
$result->free();
}
} while ($stmt->next_results());
]]>
&example.outputs;
array(1) {
[0]=>
int(1)
}
[1]=>
array(1) {
[0]=>
int(2)
}
[2]=>
array(1) {
[0]=>
int(3)
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
int(2)
}
[1]=>
array(1) {
[0]=>
int(3)
}
[2]=>
array(1) {
[0]=>
int(4)
}
}
]]>
Bien sûr, l'utilisation de l'API de liage pour la récupération est également supportée.
Procédures stockées et requête préparée en utilisant l'API de liage
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($stmt->store_result()) {
$stmt->bind_result($id_out);
while ($stmt->fetch()) {
echo "id = $id_out\n";
}
}
} while ($stmt->next_result());
]]>
&example.outputs;
Voir aussimysqli::querymysqli::multi_querymysqli::next_resultmysqli::more_resultsRequêtes multiples
MySQL autorise optionnellement le fait d'avoir plusieurs requêtes dans une
seule chaîne de requête mais nécessite une gestion spéciale.
Les requêtes multiples ou multirequêtes doivent être exécutées
avec la fonction mysqli::multi_query. Les requêtes
individuelles dans la chaîne de requête sont séparées par un point virgule.
Ensuite, tous les jeux de résultats retournés par l'exécution des requêtes
doivent être récupérés.
Le serveur MySQL autorise d'avoir des requêtes qui retournent des jeux
de résultats ainsi que des requêtes qui ne retournent aucun jeu de résultats
dans la même requête multiple.
Requêtes multiples
query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$sql = "SELECT COUNT(*) AS _num FROM test;
INSERT INTO test(id) VALUES (1);
SELECT COUNT(*) AS _num FROM test; ";
$mysqli->multi_query($sql);
do {
if ($result = $mysqli->store_result()) {
var_dump($result->fetch_all(MYSQLI_ASSOC));
$result->free();
}
} while ($mysqli->next_result());
]]>
&example.outputs;
array(1) {
["_num"]=>
string(1) "0"
}
}
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "1"
}
}
]]>
D'un point de vue de la sécurité
Les fonctions mysqli::query et
mysqli::real_query de l'API ne définissent pas de
drapeau de connexion nécessaire pour l'activation des multirequêtes sur
le serveur. Un appel supplémentaire à l'API est utilisé pour les multirequêtes
pour réduire la probabilité d'injection SQL accidentelle. Un attaquant peut
tenter d'ajouter des requêtes comme
; DROP DATABASE mysql ou ; SELECT SLEEP(999).
Si l'attaquant arrive à ajouter ce genre de SQL dans la chaîne de requête
mais que mysqli::multi_query n'est pas utilisé, le serveur
n'excutera que la première requête, mais pas la seconde représentant la requête SQL
malicieuse.
Injection SQL
query("SELECT 1; DROP TABLE mysql.user");
]]>
&example.outputs;
Prepared statements
L'utilisation des requêtes multiples avec des requêtes préparées n'est pas supportée.
Voir aussimysqli::querymysqli::multi_querymysqli_result::next-resultmysqli_result::more-resultsSupport API pour les transactions
Le serveur MySQL supporte les transactions suivant le moteur de stockage utilisé.
Depuis MySQL 5.5, le moteur de stockage par défaut est InnoDB.
InnoDB a un support complet des transactions ACID.
Les transactions peuvent soit être contrôlées en utilisant SQL, soit par des appels API.
Il est recommandé d'utiliser les appels API pour activer ou désactiver
le mode autocommit et pour valider et annuler les transactions.
Définir le mode autocommit via SQL ou via l'API
autocommit(false);
// Ne sera pas monitoré et reconnu par le plugin de réplication et de balance de charge
$mysqli->query('SET AUTOCOMMIT = 0');
]]>
Les paquets de fonctionnalités additionnelles, comme les plugins de réplication
et de balance de charge peuvent monitorer les appels API. Le plugin de réplication
offre une sécurité sur les transactions lors de la balance de charge, si
les transactions sont contrôlées avec des appels API. La sécurité des
transactions lors de la balance de charge n'est pas disponible si les requêtes
SQL sont utilisées pour définir le mode autocommit, pour valider ou annuler
une transaction.
Validation et annulation d'une transaction
autocommit(false);
$mysqli->query("INSERT INTO test(id) VALUES (1)");
$mysqli->rollback();
$mysqli->query("INSERT INTO test(id) VALUES (2)");
$mysqli->commit();
]]>
Veuillez noter que le serveur MySQL ne peut pas annuler toutes les requêtes.
Quelques requêtes requièrent une validation implicite.
Voir aussimysqli::autocommitmysqli::begin_transactionmysqli::commitmysqli::rollbackLes méta-données
Un jeu de résultats MySQL contient des méta-données. Elles décrivent
les colonnes trouvées dans le jeu de résultats. Toutes les méta-données
envoyées par MySQL sont accessible via l'interface mysqli.
L'extension n'effectue que très peu (voire, pas du tout) de modification
sur les informations qu'elle reçoit. Les différences entre les versions MySQL
ne sont pas identiques.
Les méta-données peuvent être consultées via l'interface
mysqli_result.
Accès aux méta-données du jeu de résultats
connect_errno) {
echo "Échec lors de la connexion à MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
$res = $mysqli->query("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
var_dump($res->fetch_fields());
]]>
&example.outputs;
object(stdClass)#3 (13) {
["name"]=>
string(4) "_one"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(1)
["length"]=>
int(1)
["charsetnr"]=>
int(63)
["flags"]=>
int(32897)
["type"]=>
int(8)
["decimals"]=>
int(0)
}
[1]=>
object(stdClass)#4 (13) {
["name"]=>
string(4) "_two"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(5)
["length"]=>
int(5)
["charsetnr"]=>
int(8)
["flags"]=>
int(1)
["type"]=>
int(253)
["decimals"]=>
int(31)
}
}
]]>
Requêtes préparées
Les méta-données des jeux de résultats créés en utilisant des requêtes
préparées sont accessibles de la même façon. Un gestionnaire
mysqli_result utilisable est retourné par
la fonction mysqli_stmt::result_metadata.
Méta-données via des requêtes préparées
prepare("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
$stmt->execute();
$result = $stmt->result_metadata();
var_dump($result->fetch_fields());
]]>
Voir aussimysqli::querymysqli_result::fetch_fields