Comment configurer et utiliser PDO pour l’accès aux bases de données sous Linux
Objectif
Découvrez comment configurer et utiliser PDO pour l’accès aux bases de données : des modes d’erreur aux méthodes de récupération.
Exigences
- Une connaissance standard de MySQL et du client de ligne de commande
mysql; - Être familier avec les concepts fondamentaux de la programmation orientée objet
- PHP >= 5.1
- Avoir une base de données MySQL/MariaDB fonctionnelle
Difficulté
DOULEUR MOYENNE
Conventions
- # – nécessite que les commandes Linux données soient exécutées avec les privilèges root, soit
directement en tant qu’utilisateur root, soit en utilisant la commandesudo - $ – nécessite que des commandes Linux données soient exécutées en tant qu’utilisateur régulier non privilégié
Introduction
PDO est l’acronyme de PHP Data Objects : il s’agit d’une extension PHP permettant d’interagir avec des bases de données grâce à l’utilisation d’objets. L’un de ses points forts réside dans le fait qu’il n’est pas strictement lié à une base de données particulière : son interface offre un moyen commun d’accéder à plusieurs environnements différents, entre autres :
- MySQL
- SQLite
- PostgreSQL
- Serveur Microsoft SQL
Ce guide vise à fournir une vue d’ensemble assez complète de PDO, guidant le lecteur étape par étape depuis l’établissement d’une connexion à la base de données, jusqu’au choix du mode de récupération le plus approprié, en montrant comment créer des instructions préparées et en décrivant les modes d’erreur possibles.
Créer une base de données et une table de test
La première chose que nous allons faire est de créer une base de données pour ce tutoriel :
CREATE DATABASE solar_system;
GRANT ALL PRIVILEGES ON solar_system.* TO 'testuser'@'localhost'
IDENTIFIED BY 'testpassword';
Nous avons accordé à l’utilisateur testuser tous les privilèges sur la base de données solar_system, en utilisant testpassword comme mot de passe. Créons maintenant une table et remplissons-la avec quelques données (aucune précision astronomique n’est prévue) :
USE solar_system;
CREATE TABLE planets (
id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
name VARCHAR(10) NOT NULL,
color VARCHAR(10) NOT NULL
);
INSERT INTO planets(name, color) VALUES('earth', 'blue'), ('mars', 'red'), ('jupiter', 'strange');
DSN : Nom de la source de données
Maintenant que nous avons une base de données, nous devons définir un DSN. DSN est l’abréviation de Data Source Name, et il s’agit essentiellement d’un ensemble d’informations nécessaires pour se connecter à la base de données, représenté sous la forme d’une chaîne. La syntaxe peut être différente selon la base de données à laquelle vous souhaitez vous connecter, mais comme nous interagissons avec MySQL/MariaDB, nous fournirons :
- Le type de pilote à utiliser pour la connexion
- Nom d’hôte de la machine hébergeant la base de données
- Le port à utiliser pour la connexion (facultatif)
- Le nom de la base de données
- Le jeu de caractères (facultatif)
Le format de la chaîne, dans notre cas, serait le suivant (nous allons la stocker dans la variable $dsn) :
$dsn = "mysql:host=localhost;port=3306;dbname=solar_system;charset=utf8";
Tout d’abord, nous avons fourni le préfixe de la base de données. Dans ce cas, puisque nous nous connectons à une base de données MySQL/MariaDB, nous avons utilisé mysql. Nous avons ensuite séparé le préfixe du reste de la chaîne par deux points et chacune des autres sections par un point-virgule.
Dans les deux sections suivantes, nous avons spécifié le nom d’hôte de la machine sur laquelle la base de données est hébergée et le port à utiliser pour la connexion. Si ce dernier n’est pas fourni, c’est celui par défaut qui sera utilisé, qui, dans ce cas, est 3306. Immédiatement après, nous avons fourni le nom de la base de données, puis le jeu de caractères à utiliser.
Création de l’objet PDO
Maintenant que notre DSN est prêt, nous allons construire l’objet PDO. Le constructeur PDO prend la chaîne dsn comme premier paramètre, le nom de l’utilisateur sur la base de données comme deuxième paramètre, son mot de passe comme troisième, et éventuellement un tableau d’options comme quatrième :
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$pdo = new PDO($dsn, 'testuser', 'testpassword', $options);
Cependant, les options peuvent également être spécifiées après la construction de l’objet, via la méthode SetAttribute() :
$pdo->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Définition du comportement PDO en cas d’erreur
Jetons un coup d’œil à quelques-unes des options disponibles pour PDO ::ATTR_ERRMODE. Cette option est vraiment importante, car elle définit le comportement du PDO en cas d’erreurs. Les options possibles sont les suivantes :
AOP ::ERRMODE_SILENT
Il s’agit de l’option par défaut. PDO définira simplement le code d’erreur et le message d’erreur. Ils peuvent être récupérés en utilisant les méthodes errorCode() et errorInfo().
AOP ::ERRMODE_EXCEPTION
C’est, à mon avis, celui recommandé. Avec cette option, en plus de définir le code d’erreur et les infos, PDO lancera une PDOException, ce qui interrompra le flux de script, et c’est particulièrement utile en cas de transactions PDO (nous verrons de quelles transactions il s’agit plus tard dans ce tutoriel).
AOP ::ERRMODE_WARNING
Avec cette option, PDO définira le code d’erreur et les informations comme indexées PDO ::ERRMODE_SILENT, mais affichera également un AVERTISSEMENT, qui ne interrompra pas le flux du script.
Définition du mode de récupération par défaut
Un autre paramètre important peut être spécifié via PDO ::D EFAULT_FETCH_MODE. constant. Il vous permet de spécifier la méthode d’extraction par défaut à utiliser lors de l’extraction des résultats d’une requête. Voici les options les plus couramment utilisées :
PDO ::FETCH_BOTH :
Il s’agit de l’option par défaut. Avec lui, le résultat récupéré par une requête de récupération sera indexé à la fois par entier et par nom de colonne. L’application de ce mode de récupération lors de la récupération d’une ligne de la table planets nous donnerait ce résultat :
$stmt = $pdo->query("SELECT * FROM planets");
$results = $stmt->fetch(PDO::FETCH_BOTH);
Array
(
[id] => 1
[0] => 1
[name] => earth
[1] => earth
[color] => blue
[2] => blue
)
PDO ::FETCH_ASSOC :
Avec cette option, le résultat sera stocké dans un tableau associatif dans lequel chaque clé sera le nom de la colonne, et chaque valeur sera la valeur correspondante dans une ligne :
$stmt = $pdo->query("SELECT * FROM planets");
$results = $stmt->fetch(PDO::FETCH_ASSOC);Array
(
[id] => 1
[name] => earth
[color] => blue
)
AOP ::FETCH_NUM
Ce mode de récupération renvoie la ligne récupérée dans un tableau indexé 0 :
Array
(
[0] => 1
[1] => earth
[2] => blue
)
AOP ::FETCH_COLUMN
Cette méthode fetch est utile lors de la récupération des valeurs d’une colonne et renverra tous les résultats à l’intérieur d’un tableau unidimensionnel simple. Par exemple, cette requête :
$stmt = $pdo->query("SELECT name FROM planets");Renvoie le résultat suivant :
Array
(
[0] => earth
[1] => mars
[2] => jupiter
)
AOP ::FETCH_KEY_PAIR
Cette méthode de récupération est utile lors de la récupération des valeurs de seulement 2 colonnes. Il renverra les résultats sous la forme d’un tableau associatif dans lequel les valeurs récupérées de la base de données pour la première colonne spécifiée dans la requête, seront utilisées comme clés du tableau, tandis que les valeurs récupérées pour la deuxième colonne, représenteront les valeurs du tableau associatif :
$stmt = $pdo->query("SELECT name, color FROM planets");
$result = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
Reviendrait :
Array
(
[earth] => blue
[mars] => red
[jupiter] => strange
)
PDO ::FETCH_OBJECT :
Lors de l’utilisation de la constante PDO ::FETCH_OBJECT, un objet anonyme sera créé pour chaque ligne récupérée. Ses propriétés (publiques) seront nommées d’après les colonnes, et les résultats de la requête seront utilisés comme leurs valeurs. L’application de ce mode de récupération à la même requête ci-dessus nous renvoyait un résultat sous la forme :
$results = $stmt->fetch(PDO::FETCH_OBJ);stdClass Object
(
[name] => earth
[color] => blue
)
PDO ::FETCH_CLASS :
Ce mode de récupération, comme ci-dessus, attribuera la valeur des colonnes aux propriétés d’un objet, mais dans ce cas, nous devrions spécifier une classe existante qui devrait être utilisée pour créer l’objet. Démontrons-le, nous allons d’abord créer une classe :
class Planet
{
private $name;
private $color;
public function setName($planet_name)
{
$this->name = $planet_name;
}
public function setColor($planet_color)
{
$this->color = $planet_color;
}
public function getName()
{
return $this->name;
}
public function getColor()
{
return $this->color;
}
} S’il vous plaît, ignorez la naïveté du code ci-dessus et notez simplement que les propriétés de la classe Planet sont privées et que la classe n’a pas de constructeur. Essayons maintenant de récupérer les résultats.
Lorsque vous utilisez fetch() avec PDO ::FETCH_CLASS vous devez utiliser la méthode setFechMode() sur l’objet instruction avant d’essayer de récupérer les données, par exemple :
$stmt = $pdo->query("SELECT name, color FROM planets");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Planet');Nous avons fourni l’option fetch constante PDO ::FETCH_CLASS comme premier argument de la méthode setFetchMode(), et le nom de la classe qui devrait être utilisée pour créer l’objet ('Planet' dans ce cas) comme second. Maintenant, nous lançons :
$planet = $stmt->fetch();Un objet Planète doit avoir été créé :
var_dump($planet);Planet Object
(
[name:Planet:private] => earth
[color:Planet:private] => blue
)
Notez comment les valeurs récupérées à la suite de la requête ont été affectées aux propriétés correspondantes de l’objet, même si elles sont privées.
Attribution de propriétés après la construction de l’objet
La classe planet n’a pas de constructeur explicite défini, donc aucun problème lors de l’attribution des propriétés ; Mais que se passerait-il si la classe avait un constructeur dans lequel la propriété était attribuée ou manipulée ? Étant donné que les valeurs sont attribuées avant l’appel du constructeur, elles auraient été écrasées.
PDO aide à fournir la constante FETCH_PROPS_LATE : lors de son utilisation, les valeurs seront attribuées aux propriétés après la construction de l’objet. Par exemple:
class Planet
{
private $name;
private $color;
public function __construct($name = moon, $color = grey)
{
$this->name = $name;
$this->color = $color;
}
public function setName($planet_name)
{
$this->name = $planet_name;
}
public function setColor($planet_color)
{
$this->color = $planet_color;
}
public function getName()
{
return $this->name;
}
public function getColor()
{
return $this->color;
}
}Nous avons modifié notre classe Planet, en fournissant un constructeur qui prend deux arguments : le premier est name et le second est color. Ces arguments ont une valeur par défaut respectivement lune et gris : cela signifie que si aucune valeur n’est explicitement fournie, ce seront les valeurs par défaut attribuées.
Dans ce cas, si nous n’utilisons pas FETCH_PROPS_LATE, quelles que soient les valeurs récupérées de la base de données, les propriétés auront toujours les valeurs par défaut, car elles seront écrasées lors de la construction de l’objet. Vérifions-le. Tout d’abord, nous exécutons la requête :
$stmt = $pdo->query("SELECT name, color FROM solar_system WHERE name = 'earth'");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Planet');
$planet = $stmt->fetch();Ensuite, nous vidons l’objet Planet et vérifions les valeurs de ses propriétés :
var_dump($planet);
object(Planet)#2 (2) {
["name":"Planet":private]=>
string(4) "moon"
["color":"Planet":private]=>
string(4) "gray"
}Comme prévu, les valeurs récupérées de la base de données ont été remplacées par les valeurs par défaut. Maintenant, nous montrons comment ce problème peut être résolu en utilisant FETCH_PROPS_LATE (la requête est la même que ci-dessus) :
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Planet');
$planet = $stmt->fetch();
var_dump($planet);
object(Planet)#4 (2) {
["name":"Planet":private]=>
string(5) "earth"
["color":"Planet":private]=>
string(4) "blue"
}Finalement, nous avons obtenu les résultats escomptés. Mais que se passe-t-il si le constructeur de la classe n’a pas de valeurs par défaut et qu’elles doivent être fournies ? Simple : on peut spécifier les paramètres du constructeur sous la forme d’un tableau en tant que troisième argument, après le nom de la classe, dans la méthode setFetchMode(). Par exemple, laissons le constructeur changer :
class Planet
{
private $name;
private $color;
public function __construct($name, $color)
{
$this->name = $name;
$this->color = $color;
}
[...]
}Les arguments du constructeur sont maintenant obligatoires, nous exécuterions donc :
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Planet', ['moon', 'gray']);Dans ce cas, les paramètres que nous avons fournis servent simplement de valeurs par défaut, nécessaires pour initialiser l’objet sans erreur : ils seront écrasés par les valeurs récupérées de la base de données.
Récupération de plusieurs objets
Bien sûr, il est possible de récupérer plusieurs résultats en tant qu’objets, soit en utilisant la méthode fetch() à l’intérieur d’une boucle while :
while ($planet = $stmt->fetch()) {
// do stuff with the results
} ou en récupérant tous les résultats en une seule fois. Dans ce cas, comme dit ci-dessus, en utilisant la méthode fetchAll(), vous n’avez pas besoin de spécifier le mode fetch avant d’appeler la méthode elle-même, mais au moment où vous l’appelez :
$stmt->fetchAll(PDO::FETCH_CLASS|PDO_FETCH_PROPS_LATE, 'Planet', ['moon', 'gray']);
AOP ::FETCH_INTO
Avec cette méthode de récupération définie, PDO ne créera pas un nouvel objet, mais mettra à jour les propriétés d’un objet existant, mais uniquement si elles sont publiques, ou si vous utilisez la méthode magique __set à l’intérieur de l’objet.
Déclarations préparées ou directes
PDO dispose de deux façons d’exécuter des requêtes : l’une est la méthode directe, en une seule étape. L’autre, plus sûre, consiste à utiliser des instructions préparées.
Requêtes directes
Lorsque vous utilisez des requêtes directes, vous avez deux méthodes principales : query() et exec(). Le premier renvoie un objet PDOStatemnt que vous pouvez utiliser pour accéder aux résultats via les méthodes fetch() ou fetchAll() : vous l’utilisez pour une instruction qui ne modifie pas une table, comme SELECT.
Ce dernier, en revanche, renvoie le nombre de lignes qui ont été modifiées par la requête : nous l’utilisons pour les instructions qui modifient les lignes, comme INSERT, DELETE ou UPDATE. Les instructions directes ne doivent être utilisées que lorsqu’il n’y a pas de variables dans la requête et que vous êtes absolument sûr qu’elle est sûre et correctement échappée.
Déclarations préparées
PDO prend également en charge les instructions préparées en deux étapes : c’est utile lors de l’utilisation de variables dans la requête, et c’est plus sûr en général, car la méthode prepare() effectuera tous les échappements nécessaires pour nous. Voyons comment les variables sont utilisées. Imaginons que nous voulions insérer les propriétés d’un objet Planet dans la table Planets. Tout d’abord, nous préparerions la requête :
$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(?, ?)");
Comme dit précédemment, nous utiliserions d’abord la méthode prepare() qui prend la requête sql comme argument, en utilisant des espaces réservés pour les variables. Désormais, les espaces réservés peuvent être de deux types :
Espaces réservés de position
En utilisant des espaces réservés positionnels, nous pouvons obtenir un code plus concis, mais nous devons fournir les valeurs à substituer dans le même ordre que les noms de colonnes, dans un tableau fourni comme argument de la méthode execute() :
$stmt->execute([$planet->name, $planet->color]);
Espaces réservés nommés
En utilisant des espaces réservés nommés, nous n’avons pas à respecter un ordre particulier, mais nous allons créer un code plus détaillé. Lors de l’exécution de la méthode execute(), nous devons fournir les valeurs sous la forme d’un tableau associatif dans lequel chaque clé serait le nom de l’espace réservé utilisé, et la valeur associée serait celle à substituer dans la requête. Par exemple, la requête ci-dessus deviendrait :
$stmt = $pdo->prepare("INSERT INTO planets(name, color) VALUES(:name, :color)");
$stmt->execute(['name' => $planet->name, 'color' => $planet->color]);
Les méthodes prepare et execute peuvent être utilisées à la fois lors de l’exécution de requêtes qui modifient ou récupèrent simplement des données de la base de données. Dans le premier cas, nous utilisons les méthodes de récupération que nous avons vues ci-dessus pour récupérer les données, tandis que dans le second, nous pouvons récupérer le nombre de lignes affectées en utilisant la méthode rowCount().
Les méthodes bindValue() et bindParam()
Pour fournir les valeurs à substituer dans la requête, nous pouvons également utiliser les méthodes bindValue() et bindParam(). La première lie la valeur de la variable fournie à l’espace réservé positionnel ou nommé associé utilisé lors de la préparation de la requête. En utilisant l’exemple ci-dessus, nous aurions fait :
$stmt->bindValue('name', $planet->name, PDO::PARAM_STR);
Nous lions la valeur de $planet->name à l’espace réservé :name. Notez qu’en utilisant les méthodes bindValue() et bindParam(), nous pouvons spécifier, comme troisième argument, le type de la variable, en utilisant la constante PDO associée, dans ce cas PDO ::P ARAM_STR.
En utilisant bindParam(), à la place, nous pouvons lier la variable à l’espace réservé associé utilisé lors de la préparation de la requête. Notez que dans ce cas, la variable est liée par référence, et que sa valeur ne sera substituée à l’espace réservé qu’au moment où la méthode execute() l’appelle. La syntaxe est la même que ci-dessus :
$stmt->bindParam('name', $planet->name, PDO::PARAM_STR)
Nous avons lié la variable $planet->name à l’espace réservé :name, pas à sa valeur actuelle ! Comme dit ci-dessus, la conversion sera effectuée juste au moment où la méthode execute() sera appelée, donc l’espace réservé sera substitué par la valeur que la variable a à ce moment-là.
Transactions PDO
Transactions permet de préserver la cohérence lors de l’émission de plusieurs requêtes. Toutes les requêtes sont effectuées dans un « lot », et validées dans la base de données uniquement si toutes aboutissent. Les transactions ne fonctionneront pas dans toutes les bases de données et pas pour toutes les constructions sql, car certaines d’entre elles provoquent un commit implicite (liste complète ici)
Avec un exemple extrême et étrange, imaginez que l’utilisateur doit sélectionner une liste de planètes, et chaque fois qu’il soumet une nouvelle sélection, vous voulez supprimer la précédente de la base de données avant d’insérer la nouvelle. Que se passerait-il si la suppression réussissait, mais pas l’insertion ? Nous aurions un utilisateur sans planètes ! En général, voici comment les transactions sont mises en œuvre :
$pdo->beginTransaction();
try {
$stmt1 = $pdo->exec("DELETE FROM planets");
$stmt2 = $pdo->prepare("INSERT INTO planets(name, color) VALUES (?, ?)");
foreach ($planets as $planet) {
$stmt2->execute([$planet->getName(), $planet->getColor()]);
}
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
}Tout d’abord, la méthode beginTransaction() de l’objet PDO désactive la requête autocommit, puis à l’intérieur d’un bloc try-catch, les requêtes sont exécutées dans l’ordre souhaité. À ce stade, si aucune PDOException n’est levée, les requêtes sont validées avec la méthode commit(), sinon, via la méthode rollBack(), les transactions sont annulées et autocommit est restauré.
De cette façon, il y aura toujours une cohérence lors de l’émission de plusieurs requêtes. Il est tout à fait évident que vous ne pouvez utiliser les transactions PDO que lorsque PDO ::ATTR_ERRMODE est défini sur PDO ::ERRMODE_EXCEPTION.
