I. Introduction▲
I-1. Prérequis▲
Le lecteur devra avoir une maitrise correcte de PHP, quelques notions d'objets, une connaissance d'un SGBD. Cet article est d'un niveau intermédiaire.
I-2. Pourquoi cet article ?▲
Je me suis rendu compte au travers du forum php de développez.com que les questions concernant des problèmes de compatibilité entre SGBD étaient récurrents ainsi que les problèmes de portage de site. Cet article ne traitera pas des étapes d'installation et configuration de chaque SGBD mais uniquement de leur utilisation par php.
I-3. Qu'est-ce que PEAR ?▲
Afin de définir PEAR, je citerais un article de ce même forum :
PEAR (pour PHP Extension and Application Repository) est une bibliothèque de scripts PHP. Tous les scripts déposés dans PEAR respectent un certain nombre de règles qui les rend portables (indépendants de la plate-forme) et réutilisables.
Cet article vous permettra d'ailleurs d'installer de facon manuelle les packages qui vous intéressent.
I-4. Problématique abordée▲
Lors d'un développement de site php/mysql, il est courant d'utiliser les API standards : mysql_connect, mysql_query, mysql_fetch_array etc...
Cependant, lors d'un portage vers un autre SGBD et parfois même lors d'une montée de version du même SGBD, l'utilisation des API pose problème puisqu'une réécriture s'impose afin de rendre le code compatible. C'est ici qu'interviennent les couches d'abstraction de données.
MDB2 fait partie des ces librairies vous permettant de vous affranchir de ces problèmes de compatibilité. Attention toutefois, l'utilisation de cette couche d'abstraction aux données vous permettra d'éviter les problèmes d'API mais pas celle de compatibilité des requêtes. Si par exemple vous utilisez la fonction NOW() dans vos requêtes SQL mysql, celle ci ne passera pas sous ORACLE.
Dans le cadre de cet article nous parlerons donc d'une couche d'abstraction de donnée fournie par PEAR : MDB2, issu de PEAR::DB et Metabase.
II. Caractéristiques▲
II-1. Drivers▲
MDB2 s'utilise avec des drivers. Un driver n'est autre qu'une interface (objet dans le cas de MDB2) permettant l'accès à différentes bases de données. Les bases de données actuellement disponibles pour MDB2 sont :
- MySQL
- PostgreSQL
- Oracle
- Frontbase
- Querysim
- Interbase/Firebird
- MSSQL
- SQLite
II-2. Fonctionnalités▲
MDB2 est une librairie orientée objet. Sa grande force repose sur la portabilité. Voici quelques unes des caractéristiques principales listées dans la doc officielle :
- Abstraction des types de données et conversion sur demande
- Multiples options de fetch (par nom, par indice, les deux etc...)
- Code d'erreurs portables
- Emulation du mode prepare execute pour les SGBD qui ne le supportent pas
- Emulation des séquences
- Emulation des sous requêtes
- Support des transactions
- Support des Large Objects
- Support des clés uniques, des index, des clés primaires
- Emulation de l'auto increment
- Emulation de LIMIT
- Fonctions de lecture des informations schémas
- Fonctions de gestion du SGBD (création, altération etc...)
- Fonctions d'engenierie inversé a partir d'une base de données existantes
Le paquet est documenté avec phpdoc. Cette documentation technique de l'API peut être trouvé sur le site de PEAR.
III. Cas d'usage▲
III-1. Connexion▲
Pour se connecter par MDB2, il faut utiliser un DSN. La documentation la plus complète sur les DSN est celle de la documentation de PEAR::DB : http://pear.php.net/manual/fr/package.database.db.intro-dsn.php.
Pour résumer le DSN est une chaine de caractères comportant les parametres de connexion à votre base de données.
Pour un utilisateur de mysql avec un accès classique, la chaine sera donc :
mysql
:
//
username:password@hostspec/database
Cependant ce formalisme vous permettra aussi d'utiliser des connexions par socket, des connexions sur port non standard, des options de lancement, une connexion ssl etc...
A noter que cette représentation est utilisée par les autres projets PEAR (Auth, LiveUser, LiveAdmin etc...)
La connexion se réalise ensuite avec la fonction statique MDB2::Connect. Celle ci accepte une chaine pour le $dsn ou un array.
Les deux syntaxes suivantes sont donc équivalentes :
$dsn
=
'
mysql
:
/
/
someuser
:
apasswd
@
localhost
/
thedb
'
;
$options
=
array(
'
debug
'
=
>
2,
'
portability
'
=
>
MDB2_PORTABILITY_ALL,
);
$db
=
&
MDB2:
:
connect
($dsn
,
$options
);
$dsn
=
array(
'
phptype
'
=
>
'
mysql
'
,
'
username
'
=
>
'
someuser
'
,
'
password
'
=
>
'
apasswd
'
,
'
hostspec
'
=
>
'
localhost
'
,
'
database
'
=
>
'
thedb
'
,
);
$options
=
array(
'
debug
'
=
>
2,
'
portability
'
=
>
MDB2_PORTABILITY_ALL,
);
$db
=
&
MDB2:
:
connect
($dsn
,
$options
);
Le & signifie que vous récupérez une référence sur l'objet et non une copie. Il est important de le conserver.
$options est facultatif, c'est un tableau associatif : parametre => valeur.
L'option la plus intéressante concerne la portabilité. Ici nous avons demandé d'activer toutes les options de portabilité (MDB2_PORTABILITY_ALL).
Voici quelques options possibles :
- MDB2_PORTABILITY_DELETE_COUNT : force les requêtes de delete a renvoyer le nombre de lignes supprimé.
- MDB2_PORTABILITY_EMPTY_TO_NULL : convertit les valeurs vides en valeur null.
- MDB2_PORTABILITY_ERRORS : rend compatible les messages d'erreurs
- MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES : supprime les informations de table et base de données des index associatif
- MDB2_PORTABILITY_FIX_CASE : convertit les noms des tables et des champs dans la casse définit par l'option "field_case"
- MDB2_PORTABILITY_NONE : désactive les options de compatibilité
- MDB2_PORTABILITY_RTRIM : supprime les blancs a droite des résultats
Remarque : Il est aussi possible de travailler avec un singleton :
$db
=
&
MDB2:
:
singleton($dsn
);
Ou avec une factory :
$db
=
&
MDB2:
:
factory($dsn
);
La déconnexion se fait avec la fonction disconnect :
$db
-
>
disconnect();
III-2. Requêtes▲
MDB2 vous permet d'interroger la base de données grace à différentes fonctions. On peut citer les classiques :
query (utilisé pour des select, update, insert, delete )
queryAll (utilisé pour des select, renvoie un tableau associatif avec les valeurs demandés)
queryCol (renvoie un tableau de valeurs correspondant a la colonne demandé)
queryOne (renvoie un seul résultat pour la première colonne, première ligne)
queryRow (renvoie la première ligne)
exec (utilisé pour des update, insert, delete ). Cette fonction vous renvoie le nombre de lignes affectés.
Pour toutes ces requêtes, il est très important d'avoir spécifié avant le mode de FETCH.
Par exemple pour obtenir un tableau associatif :
$db
-
>
setFetchMode(MDB2_FETCHMODE_ASSOC);
Les modes suivants sont disponibles :
MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT , MDB2_FETCHMODE_DEFAULT , MDB2_FETCHMODE_FLIPPED.
Ainsi pour effectuer un update, nous ferons :
$result
=
$db
-
>
exec ( "
UPDATE
machin
set
truc
=
'
bidule
'
"
);
//
$result
contient
le
nombre
de
lignes
mise
à
jour.
Pour une selection :
IV. Cas d'utilisation plus poussé▲
Nous pourrions nous arrêter la et pour la plupart des utilisateurs qui ne désire que utiliser les fonctionnalités de base, la lecture du second chapitre suffit. Cependant, pour aller plus loin, l'utilisation de MDB2 permet, quelque soit le système, d'utiliser des fonctionnalités intéressantes. Nous allons en détailler quelques unes.
IV-1. La gestion des erreurs▲
La gestion des erreurs respecte le formalisme de PEAR. Si vous avez déjà utilisé des paquets PEAR, vous ne serez pas dépaysé.
La fonction statique isError permet de déterminer si une valeur de retour est une erreur MDB2. Cet objet erreur contient des informations intéressantes, notamment un message d'erreur.
Exemple :
$db
=
MDB2:
:
connect
($this
-
>
dsn,
$options
);
//
Vérification
des
erreurs
if (MDB2:
:
isError($db
))
{
echo $db
-
>
getDebugInfo().
'
<
BR
/
>
'
;
echo $db
-
>
getMessage();
}
$result
=
$db
-
>
query
($sql
);
//
Vérification
des
erreurs
if (MDB2:
:
isError($result
))
{
echo $result
-
>
getDebugInfo().
'
<
BR
/
>
'
;
echo $result
-
>
getMessage();
}
L'objet MDB2_Error dérive lui même de PEAR_Error c'est à dire qu'il possède les mêmes méthodes et les mêmes attributs. La gestion des erreurs est différente suivant un parametre qu'on peut positionner en début de programme.
PEAR:
:
setErrorHandling(PEAR_ERROR_PRINT);
Le paramêtre passé a cette fonction va déterminer le comportement attendu en cas d'erreur.
- PEAR_ERROR_CALLBACK
- PEAR_ERROR_DIE
- PEAR_ERROR_TRIGGER
- PEAR_ERROR_RETURN
- PEAR_ERROR_PRINT
Ces différentes options permettront en cas d'erreur d'appeler une fonction de callback, de stopper le script par un die, de renvoyer un code d'erreur ou d'afficher une erreur. La fonction de callback demande que l'on précise une fonction de callback en second paramêtre.
IV-2. Les transactions et les insertions par tableaux▲
Une transaction est un regroupement de requêtes qu'on désire exécuter si et seulement si aucune n'échoue. Au contraire d'une requête atomique exécute quelque soit le résultat des autres requêtes. Ces transactions sont émulées par MDB2, il faut cependant que votre SGBD les supporte.
Une transaction se débute par beginTransaction et se termine par la propagation des résultats (commit).
Un retour en arrière peut se faire avec la fonction rollback.
Les transactions sont intéressantes à utiliser avec les insertions par tableau de valeurs (host array). Ici il s'agit de préparer une requête génériques et de donner un ensemble de valeur utilisé pour cette requête. Si votre SGBD est optimisé pour ce type d'opérations, celles ci sont bien plus rapides. Et le mode transactionnel vous assure de la réussite totale ou non de votre demande empêchant ainsi de vous retrouver dans un état intermédiaire.
Exemple d'utilisation :
$alldata
=
array(
array(1,
'
one
'
,
'
un
'
),
array(2,
'
two
'
,
'
deux
'
),
array(3,
'
three
'
,
'
trois
'
),
array(4,
'
four
'
,
'
quatre
'
)
);
$stmt
=
$mdb2
-
>
prepare
('
INSERT
INTO
numbers
VALUES
(
?
,
?
,
?
)
'
,
array('
integer
'
,
'
text
'
,
'
text
'
),
false);
foreach ($alldata
as $row
)
{
echo('
running
execute
<
br
>
'
);
$stmt
-
>
bindParamArray($row
);
$stmt
-
>
execute
();
}
IV-3. Les sous requêtes▲
Les sous requêtes ne sont pas gérés par tous les SGBD mais MDB2 permet de les émuler :
$sub_select
=
$mdb2
-
>
subSelect('
SELECT
test_name
from
test
WHERE
test_name
=
'
.
$mdb2
-
>
quote('
gummihuhn
'
,
'
text
'
),
'
text
'
);
$query_with_subselect
=
'
SELECT
*
FROM
test
WHERE
test_name
IN
(
'
.
$sub_select
.
'
)
'
;
$result
=
$mdb2
-
>
query
($query_with_subselect
);
$array
=
$result
-
>
fetchAll();
$result
-
>
free
();
IV-4. La clause LIMIT▲
La clause LIMIT connu en mysql n'est pas disponible sur tous les SGBD. Elle est donc émulée par MDB2 de la facon suivante :
$db
-
>
setLimit(3,
1);
$result
=
$db
-
>
query
('
SELECT
*
FROM
users
'
);
$numrows
=
$result
-
>
numRows();
//
numrows
doit
etre
égal
a
3
dans
notre
exemple
IV-5. Les modules▲
IV-5-a. Gestion de base de données▲
Le paquet MDB2 peut utiliser des modules avec des fonctionnalités intéressantes. L'un d'entre eux permet la gestion de base de données :
Ce manager est une référence vers le gestionnaire interne de MDB2. Nous pourrons donc y faire référence de facon directe $manager-> ou bien par $db->manager->
Son API est décrite ici.
Ces fonctions vont nous permettre de créer une table a partir d'un tableau, de créer une base, de créer des séquences (émulé pour certains SGBD), de créer des contraintes etc... Ce gestionnaire est donc un parfait couteau suisse pour la gestion du schéma à proprement parler.
Voici par exemple une création de table :
$this
-
>
fields =
array(
'
id
'
=
>
array(
'
type
'
=
>
'
integer
'
,
'
unsigned
'
=
>
true,
'
notnull
'
=
>
true,
'
default
'
=
>
0,
),
'
textfield
'
=
>
array(
'
type
'
=
>
'
text
'
,
'
length
'
=
>
12,
),
'
booleanfield
'
=
>
array(
'
type
'
=
>
'
boolean
'
,
),
'
decimalfield
'
=
>
array(
'
type
'
=
>
'
decimal
'
,
),
'
floatfield
'
=
>
array(
'
type
'
=
>
'
float
'
,
),
'
datefield
'
=
>
array(
'
type
'
=
>
'
date
'
,
),
'
timefield
'
=
>
array(
'
type
'
=
>
'
time
'
,
),
'
timestampfield
'
=
>
array(
'
type
'
=
>
'
timestamp
'
,
),
);
$db
-
>
manager-
>
createTable('
exemple
'
,
$this
-
>
fields);
Nous remarquons dans cet exemple l'utilisation des types MDB2 pour la définition des champs, garantissant ainsi une portabilité sur l'ensemble des SGBD supporté.
Un autre exemple intéressant est l'utilisation de ces types MDB2 combiné avec une représentation sous forme de tableau pour exécuter des modifications de tables (ALTER):
$changes
=
array(
'
add
'
=
>
array(
'
quota
'
=
>
array(
'
type
'
=
>
'
integer
'
,
'
unsigned
'
=
>
1,
),
'
note
'
=
>
array(
'
type
'
=
>
'
text
'
,
'
length
'
=
>
'
20
'
,
),
),
'
rename
'
=
>
array(
'
sex
'
=
>
array(
'
name
'
=
>
'
gender
'
,
'
definition
'
=
>
array(
'
type
'
=
>
'
text
'
,
'
length
'
=
>
1,
'
default
'
=
>
'
M
'
,
),
),
),
'
change
'
=
>
array(
'
id
'
=
>
array(
'
unsigned
'
=
>
false,
'
definition
'
=
>
array(
'
type
'
=
>
'
integer
'
,
'
notnull
'
=
>
false,
'
default
'
=
>
0,
),
),
'
somename
'
=
>
array(
'
length
'
=
>
'
20
'
,
'
definition
'
=
>
array(
'
type
'
=
>
'
text
'
,
'
length
'
=
>
20,
),
)
),
'
remove
'
=
>
array(
'
somedescription
'
=
>
array(),
),
'
name
'
=
>
$newer
,
);
$result
=
$db
-
>
manager-
>
alterTable('
exemple2
'
,
$changes
,
true);
Pour les personnes familières avec les séquences, MDB2 en propose aussi une émulation :
$result
=
$db
-
>
manager-
>
createSequence($seq_name
);
$array
=
$db
-
>
manager-
>
listSequences();
$id
=
$db
-
>
currID($seq_name
);
//
récupération
de
la
valeur
courante
de
la
séquence
$id
=
$db
-
>
nextID ($seq_name
);
//
incrémentation
de
la
séquence
$result
=
$db
-
>
manager-
>
dropSequence($seq_name
);
IV-5-b. Le module fonction▲
Inutile de s'attarder longuement sur ce module, il s'agit d'un ensemble de fonctions pratiques réécrites pour être portable sur chaque SGBD. Il s'agit de :
- concat
- executeStoredProc
- functionTable
- now
- substring
IV-5-c. Le module reverse▲
Ce module permet de récupérer des informations sur le schéma. Il est souvent couplé avec le module Manager.
$table_info
=
$db
-
>
reverse-
>
tableInfo('
users
'
);
IV-5-d. Les Large Objects▲
L'un des types de données sur lequel nous pouvons nous attarder est certainement celui des Large Objects. Ce type de champ permet de stocker des fichiers en base. 2 types de LOB sont utilisés :
- les BLOBS (fichiers binaires)
- les CLOBS (fichiers texte normaux)
Avec MDB2, ces objets peuvent être insérés en utilisant les requêtes préparées (voir le point 2).
$query
=
$db
-
>
prepareQuery('
INSERT
INTO
files
(
id
,
b_data
,
c_data
)
VALUES
(
1
,
?
,
?
)
'
,
array('
blob
'
,
'
clob
'
),
MDB2_PREPARE_MANIP,
array('
b_data
'
,
'
c_data
'
));
$db
-
>
bindParam(0,
$binary_lob
);
//
binary_lob
contiendra
le
contenu
d'un
fichier
(ouvert
par
fopen
par
exemple)
$db
-
>
bindParam(1,
$character_lob
);
//
character_lob
contiendra
une
longue
chaine
de
caractère
$result
=
$db
-
>
executeQuery($query
);
Pour la sélection d'un LOB, nous utiliserons la même méthode que pour une requête de sélection classique.
$result
=
$db
-
>
query
('
SELECT
b_data
,
c_data
FROM
files
WHERE
id
=
1
'
,
array(blob,
clob));
$row
=
$result
-
>
fetchRow();
$blob
=
$row
[
0]
;
$clob
=
$row
[
1]
;
IV-5-e. La maintenance de schéma indépendante du SGBD▲
Remarque :
Le paquet MDB2_Schema est, à l'heure actuelle, toujours en version béta. Son utilisation n'est donc pas sans risque.
Grace a MDB2, il est désormais possible de représenter son schéma à partir d'un fichier descriptif en format XML.
Le paquet Schema directement dépendant de MDB2 permet ainsi de maintenir sa base de données à partir d'un formalisme non lié au SGBD utilisé.
L'une de ces utilisations permet de faire de l'ingénierie inversée à partir d'une base existante :
$schema
=
&
MDB2_Schema:
:
factory($dsn
);
Vous remarquerez ici que nous utilisons MDB2_Schema qui est un paquet utilisant MDB2.
$dump_config
=
array(
'
output_mode
'
=
>
'
file
'
,
'
output
'
=
>
'
monfichier
.
xml
'
);
$operation
=
$schema
-
>
dumpDatabase($dump_config
,
MDB2_SCHEMA_DUMP_STRUCTURE);
Nous pouvons noter ici qu'il est possible de récupérer la structure du schéma, les données, ou les deux grâce au second argument :
- MDB2_SCHEMA_DUMP_STRUCTURE
- MDB2_SCHEMA_DUMP_CONTENT
- MDB2_SCHEMA_DUMP_ALL
Cette représentation sous forme XML utilisera les types de données MDB2. Ceux-ci seront convertis dans leurs équivalents sur le SGBD utilisé lors d'un éventuel chargement avec updateDatabase.
Ce chargement se fera avec la syntaxe suivante :
$operation
=
$schema
-
>
updateDatabase('
monfichier
.
xml
'
,
'
monfichier
.
xml
.
old
'
,
array(),
false );
Cette opération crééra le fichier 'monfichier.xml.old', copie conforme du fichier initial.
Attention, cette même fonction est utilisé pour la mise à jour du schéma. Si le fichier 'monfichier.xml.old' existe, updateDatabase établira les différences entre les deux fichiers et feras une mise a jour de la base (ALTER).
Liens utiles▲
Merci à Lukas Smith pour ces remarques qui ont permis de corriger certaines parties de cet article.