La communication entre un client, l'application, et le SGBD, le serveur, s'établit en utilisant les protocoles réseau classiques :
Les échanges avec le SGBD ne sont pas normalisés : chaque éditeur a mis en place ses propres stratégies pour établir le dialogue, formater les données... C'est pourquoi nous sommes obligés d'installer, sur chaque poste client, le logiciel qui permettra d'établir la communication avec le SGBD.
Lorsque les mécanismes Objet ont été décrits, et notamment avec l'avènement de UML, certains chercheurs ont essayé d'abord de créer des bases de données objet, dont le résultat a été très mitigé : les bases de données manipulant des objets sont assez rares, tout simplement parce que le langage SQL, s'il n'est pas parfait, présente l'avantage d'être simple, efficace, et universel. Aujourd'hui, les mécanismes objets ne sont implémentés que pour des usages bien spécifiques. Certains auteurs considèrent que les annuaires à la norme LDAP peuvent être assimilés à des bases de données Objet. On retrouve également ce mécanisme implémenté dans des bases de données SQL classiques : ORACLE, par exemple, a introduit un objet appelé « cartouche spatial », qui permet de manipuler les informations utilisées dans les systèmes d'informations géographiques. POSTGRESQL dispose, lui aussi, de son objet géographique, implémenté dans l'extension POSTGIS.
Le langage SQL, dans sa version 3 (SQL-3 ou SQL-99) a intégré quelques mécanismes issus de la recherche sur les objets, comme l'héritage, qui se retrouve aujourd'hui dans le SGBD POSTGRESQL.
POSTGRESQL implémente un concept intéressant, l'héritage de tables. Par exemple, une table Personne pourra être héritée dans une table Beneficiaire, cette dernière étant liée aux dossiers d'aide ou aux RIB utilisés pour le paiement. Ces deux tables, Personne et Beneficiaire, pourront être manipulées de façon très simple avec les extensions SQL de la norme SQL:1999.
Toutefois, la norme SQL:1999 n'a quasiment jamais été implémentée. Concevoir des bases de données selon ce système revient donc à travailler exclusivement avec la base de données POSTGRESQL : ce n'est pas forcément une mauvaise solution, mais il faut que les avantages apportés par le choix de ce gestionnaire de bases de données soient déterminants. Ce SGBD est souvent choisi d'ailleurs en raison de la disponibilité de son objet géographique, dont nous parlerons au paragraphe suivant.
Quant à l'héritage, ce mécanisme peut être contourné en travaillant avec des tables liées. Pour reprendre nos deux tables Personne et Beneficiaire, il suffit que Beneficiaire dispose de la même clé que Personne et qu'une jointure soit réalisée entre les deux tables pour arriver quasiment au même mécanisme.
La seconde approche a été mise en œuvre principalement pour gérer le stockage des informations géographiques. Pour stocker un polygone ou un point, il faut indiquer des coordonnées, des cheminements (le contour du polygone...), et il faut disposer également de fonctions pour les manipuler. ORACLE et POSTGRESQL, avec son extension POSTGIS, ont intégré depuis quelques années un objet géographique, qui permet de manipuler une information en une seule opération, ce qui simplifie grandement les opérations dans les systèmes d'informations géographiques.
Ne pas confondre le modèle relationnel et l'application Objet !
Les modèles objet, et notamment le diagramme de classes, ressemblent fortement à un modèle relationnel (modèle conceptuel des données Merise, par exemple). La tendance est forte de dessiner un diagramme de classes, qui sera utilisé tel quel pour générer la base de données et, par extension, manipuler les données.
Cette approche présente un inconvénient majeur : elle ne tient pas compte de la nature intrinsèque des SGBD.
Les SGBD sont conçus pour répondre à une requête. Cette requête est émise dans un langage, le SQL, et le SGBD peut renvoyer des informations (une collection) et un code d'erreur. Le contenu de la requête n'influe en rien sur la manière dont les communications s'établissent entre l'application et le SGBD.
D'une manière générale, pour exécuter une requête SQL, c'est le fonctionnement suivant qui est le plus fréquemment mis en œuvre :
Dans cet exemple, le navigateur (l'utilisateur) demande l'ouverture d'un module (une page PHP). Ce dernier va demander à la classe d'accès à la base de données (AccesBDD) d'exécuter une requête SQL. C'est la classe AccesBDD qui va implémenter les commandes spécifiques du SGBD, et non le module directement.
Ce schéma est valable quelle que soit la requête, qu'elle soit de sélection, de modification ou de suppression... La méthode Execute va retourner une collection dans le cas d'une commande de sélection, ou simplement un code de retour en cas d'exécution d'une commande de mise à jour.
Il s'agit, ici, d'un exemple très simple : si ce schéma était conservé en l'état, il faudrait coder toutes les requêtes SQL dans tous les modules, ce qui irait à l'encontre du but recherché, à savoir un codage unique dans l'application.
Il existe plusieurs bibliothèques en PHP qui permettent d'accéder au données en intercalant une couche d'abstraction. Celle-ci va prendre en charge les différentes commandes utilisées pour se connecter et exécuter les requêtes, et permettre au développeur de s'affranchir du type de SGBD utilisé.
Plusieurs grandes bibliothèques sont disponibles en PHP actuellement : celle implémentée dans Zend Framework (classes Zend_db et connexes), qui s'appuie sur PDO (Php Data Objects, une couche d'accès intégrée dans PHP depuis PHP5), celle présente dans PEAR, ou ADODB.
L'utilisation d'une couche d'abstraction ne doit pas faire oublier que seules les requêtes standard seront prises en charge quel que soit le SGBD : il ne faut pas oublier que chaque éditeur a rajouté ses propres extensions. Néanmoins, il est très souvent possible de les masquer en les intégrant dans des vues, codées directement dans la base de données.
Nous l'avons vu, l'application d'un modèle relationnel n'a pas vraiment de sens en programmation objet. Néanmoins, un de ses objectifs est de ne coder qu'une seule fois, et de pouvoir réutiliser le code à d'autres endroits : des modules seront développés pour pouvoir retrouver une classe d'informations rapidement, quel que soit leur moment d'utilisation.
La conception de l'application va passer par les phases suivantes :
Pour comprendre le mécanisme, nous allons prendre comme exemple la gestion d'un dossier de subvention. L'application doit permettre de stocker les informations concernant un dossier de subvention. La subvention concerne un bénéficiaire, qui peut disposer de plusieurs adresses (postales, siège social, atelier...), de plusieurs comptes bancaires représentés par des relevés d'identité bancaires (RIB) (…)
De cet exemple, nous pouvons en déduire le diagramme de classe suivant :
Ce schéma permet de préciser :
En ce qui concerne la création des objets, si l'adresse avait été unique, nous n'aurions pas eu besoin de créer une classe spécifique : elle aurait fait partie des attributs de la classe Beneficiaire.
Il en est de même pour le RIB, qui pourrait être considéré comme un attribut de la classe Dossier. Mais si nous voulons rajouter un programme de vérification de la clé, afin de s'assurer que le RIB saisi soit bien correct, il vaut mieux créer alors une classe RIB qui contiendra alors la fonction permettant de vérifier sa validité.
A partir de ce diagramme, nous pouvons en déduire le schéma de la base de données :
Cette représentation permet de visualiser la table DossierBeneficiaire, qui est porteuse de la relation n-n entre les tables Dossier et Beneficiaire (cette relation particulière sera détaillée ultérieurement). Le RIB est stocké dans une table à part, pour pouvoir représenter les différents RIB possédés par un bénéficiaire. Il en est de même pour les adresses, qui sont typées (adresse postale, de résidence...).
Il n'existe pas de norme particulière, qui va « dire » comment doit être construite une base de données. Au moment de la conception des tables, quelques concepts doivent être conservés à l'esprit :
Les SGBD sont souvent sensibles à la casse (les minuscules et les majuscules sont reconnues comme des caractères différents). Cette caractéristique va pouvoir être utilisée pour rendre notre base de données lisible.
Voici un libellé de colonne, volontairement long, écrit dans trois styles différents :
releveidentitebancairedossierdepret
RELEVEIDENTITEBANCAIREDOSSIERDEPRET
ReleveIdentiteBancaireDossierDePret
Le moins lisible est celui écrit en majuscule ; c'est, hélas, une habitude fréquente d'écrire les tables et les colonnes dans cette casse, en partie d'ailleurs à cause des outils de dessin des bases de données, qui proposent fréquemment par défaut la génération des tables dans ce mode. Le troisième est le plus lisible, simplement parce que chaque mot est isolé des autres en le commençant par une majuscule. C'est une notation appelée parfois « à l'allemande », qu'il est conseillé de privilégier.
Il faut faire également attention à ne pas utiliser d'accents ou de caractères spéciaux : selon le jeu de codage des caractères utilisé, le résultat peut être parfois désastreux.
Attention toutefois si vous travaillez avec PostgreSQL : si vous employez des majuscules dans les noms des colonnes, vous devrez entourer les colonnes du caractère "double-quotes" (normalisation SQL). Cela peut être parfois un peu compliqué...
La plupart des SGBD gèrent la notion de schéma. Par défaut, les tables sont écrites dans le schéma « public », mais vous pouvez créer d'autres schémas.
Quelques conseils :
- stockez les données « métiers » dans le schéma public ;
- créez des schémas pour gérer des aspects particuliers de l'application. Par exemple, si vous avez besoin de réaliser des imports, les tables permettant de décrire ces imports pourront être utilement stockées dans un schéma dédié à cet usage, par exemple « import ». De même, si la gestion des droits nécessite des tables particulières, stockez les dans un schéma adéquat ;
- créez également des schémas pour des usages spécifiques, par exemple pour isoler un sous-ensemble de la base de données destiné à être exporté. Dans ce schéma, vous pourrez ne créer que des vues (avec critères de sélection spécifiques), qui permettront d'obtenir un sous-ensemble de vos données en fonction de critères déterminés à l'avance.
Au moment de créer une base de données, il vaut mieux :
et, bien sûr, respecter les règles qui auront été fixées...
Chaque SGBD disposant de son propre jeu d'instructions, les programmeurs ont depuis longtemps cherché à s'affranchir le plus possible de la base de données pour réaliser un code portable : l'application peut être développée en s'appuyant sur une base de données PostGreSQL, puis porter l'application sous Oracle ou Sybase, par exemple.
En PHP, les initiatives pour s'abstraire du type de la base de données sont nombreuses. Dans la pratique, elles présentent toutes à peu près le même fonctionnement :
Les bibliothèques d'abstraction les plus connues, aujourd'hui, sont les suivantes :
$stmt=$bdd->prepare("INSERT INTO Beneficiaire (nom,prenom) VALUES (?, ?)");
$stmt->bindParam(1, $nom);
$stmt->bindParam(2,$prenom);
//insertion d'une ligne
$nom='Dupont'';
$prenom='Jean';
$stmt->execute();
ADODB (http://adodb.sourceforge.net/) est une bibliothèque d'abstraction d'accès aux bases de données, disponible sous PHP et PYTHON. Elle encapsule les accès aux différentes bases de données, sans que le développeur ait besoin de connaître les différentes commandes spécifiques à chacune.
Elle permet d'accéder à quasiment toutes les bases de données disponibles : MySQL, PostgreSQL, Interbase, Firebird, Informix, Oracle, MS SQL, Foxpro, Access, ADO, Sybase, FrontBase, DB2, SAP DB, SQLite, Netezza, LDAP, ODBC, ODBTP. Il est également possible de rajouter ses propres connecteurs, si c'est nécessaire. C'est, à ce jour, très certainement la bibliothèque la plus riche disponible en PHP.
La gestion des exceptions est également disponible sous PHP5. Une version compilée, en langage C, peut également être installée sur le serveur web, ce qui permet d'accélérer nettement son exécution : la bibliothèque reconnaît automatiquement la présence du programme compilé et lui transmet l'exécution des instructions.
De façon simple, pour utiliser ADODB, nous avons besoin de quelques lignes de code.
Pour commencer, les informations concernant la base de données sont stockées dans des variables :
$BDD_type = "mysql"; // Base de données de type MYSQL
$BDD_server = "localhost"; // Adresse du serveur de base de données
$BDD_login = "proto"; // Login de connexion à la base de données
$BDD_passwd = "proto"; // Mot de passe associé au login
$BDD_database = "proto"; // Nom de la base de données
Puis les bibliothèques ADODB sont chargées, ici depuis le dossier plugins/adodb :
include_once('plugins/adodb/adodb.inc.php');
et la connexion à la base de données est établie :
if (!isset($bdd)) {
// La connexion n'existe pas
$bdd = ADONewConnection($BDD_type);
$etatconn=$bdd->Connect($BDD_server, $BDD_login, $BDD_passwd, $BDD_database);
if ($etatconn == FALSE) {
echo( "La connexion a la base de donnees a echoue" );
die ();
}
}
L'application est maintenant connectée à notre base de données.
Nous allons exécuter maintenant une requête de sélection. Tout d'abord, la requête SQL est préparée :
$sql = "select * from Dossier";
Nous indiquons à notre objet ADOConnexion de travailler dans un mode particulier : chaque colonne sera identifiée par son nom (et non par le numéro de champ)
$bdd->SetFetchMode(ADODB_FETCH_ASSOC);
La requête SQL est exécutée. Une instance de l'objet ADORecordSet, contenant le résultat de cette requête va être créé, $rs :
$rs = $bdd->execute($sql);
if(!$rs)
{
$collection=false;
print $bdd->ErrorMsg();
} else {
$collection = array();
$collection = $rs->GetRows($rs->RecordCount());
}
$collection est un tableau indexé à deux dimensions. La première dimension correspond aux lignes retournées (numérotées de 0 à n), la seconde aux champs de chaque ligne.
Nous l'avons vu, la gestion de l'ensemble des opérations concernant une table, à savoir la lecture ou l'écriture des informations nécessite l'écriture d'un code particulièrement important, et qui est particulièrement fastidieux à rédiger.
Ainsi, pour une requête d'écriture, il faut :
La tentation est grande de vouloir encapsuler ces opérations dans une classe générique, qui réalisera toutes ces opérations à notre place.
Dans le cadre de la programmation objet, ce mécanisme est parfois appelé couplage relationnel-objet (ou ORM en anglais, Object Relational Mapping).
DOCTRINE : s'appuyant sur PDO pour les accès aux bases de données, cette bibliothèque gère les relations entre les tables et crée automatiquement les jointures, en les déduisant d'un schéma défini à partir des classes d'accès. Il faut décrire la structure et les relations dans les classes dérivées (par exemple, Beneficiaire).
EzComponents : il s'agit d'un ensemble de bibliothèques PHP, qui dispose d'un module d'accès aux données.
EZPDO : cette bibliothèque est basée elle aussi sur PDO. La structure de la base de données doit être décrite dans des zones de commentaires (balise @ORM), selon la norme de documentation PhpDocumentor (cf. chapitre 6, la structuration de l'application).
JDAO : intégré à Jelix, un atelier d'application 100 % objet. JDAO est basé sur une description de la base de données dans un fichier XML.
METASTORAGE : c'est une application qui va générer automatiquement les classes d'accès aux données, à partir d'un schéma XML.
PHPMYOBJECT : il fonctionne en natif avec les bases de données MySQL ou PostgreSQL, et peut s'appuyer sur les pilotes PDO. Il déduit intégralement la structure des tables en interrogeant la base de données (si la structure ne peut être déduite, elle peut être décrite dans une classe).
PROPEL : intégrée à Symphony un framework (squelette d'application - cf. chapitre 8, La conception selon le modèle MVC – les frameworks), elle s'appuie sur un fichier XML pour décrire la base de données, qui peut être généré automatiquement.
ZENDdbTable : ce composant est intégré à Zend Framework, s'appuie sur PDO pour les accès aux données, et dispose de fonctions avancées pour gérer les contraintes au sein de la base de données.
La classe OBJETBDD (http://objetbdd.sourceforge.net), étudiée ici, présente l'avantage de s'appuyer sur ADODB pour gérer les accès à la base de données ; elle peut donc, en théorie, être utilisée quel que soit le moteur de base de données utilisé.
De plus, la plupart des classes ORM s'appuient sur des mécanismes qui récupèrent automatiquement le type des colonnes. Malheureusement, cela ne fonctionne pas avec toutes les bases de données ; ObjetBDD permet de contourner ce problème, mais impose en contrepartie une déclaration manuelle des colonnes qui ne sont pas de type texte pour fonctionner correctement.
Une autre différence tient au mode d'insertion des données. La plupart des ORM utilisent une assignation de variable directe (par exemple, $maclasse->nom = 'Dupont'), ObjetBDD travaille avec des tableaux ($donnees = array('nom'=>'Dupont').
Enfin ObjetBDD réalise un certain nombre de contrôles avant l'insertion en base de données, gère automatiquement la transformation des dates depuis le format de la base de données vers l'affichage de l'utilisateur, et encode les caractères spéciaux HTML pour limiter les risques d'attaque par « cross site scripting ».
OBJETBDD est une classe non instanciable, qui doit donc par conséquent être héritée. Elle s'insère ainsi au sein de nos objets :
ObjetBDD utilise une instance ADOConnection pour réaliser les opérations sur la base de données.
L'ensemble des objets qui vont être utilisés pour manipuler les informations dans la base de données vont hériter de ObjetBDD.
exemple de création de la classe Beneficiaire :
Pour utiliser cette classe :
include_once('objetBDD/ObjetBDD.php');
class Beneficiaire extends ObjetBDD {
function __construct($bdd, $param=NULL){
$this->table="Beneficiaire";
$this->colonnes = array(
"IdBeneficiaire"=>array("type"=>1,"requis"=>1),
"DateNaissanceBeneficiaire"=>array("types"=>2),
"NomBeneficiaire"=>array("longueur"=>30,"pattern"=>"#^[a-zA-Z]+$#","requis"=>1),
"PrenomBeneficiaire"=>array("longueur"=>20),
"MelBeneficiaire"=>array("pattern"=>"#^.+@.+\.[a-zA-Z]{2,6}$#")
);
parent::__construct($link, $param);
Les différents types d'informations qui peuvent être déclarés pour chaque colonne sont les suivants :
Par défaut, OBJETBDD adopte le comportement suivant :
Les principales méthodes disponibles :
Exemple d'utilisation :
include_once('adodb.inc.php');
$bdd = ADONewConnection($BDD_type);
$bdd->Connect($BDD_server, $BDD_login, $BDD_passwd, $BDD_database);
include_once('ObjetBDD.php');
include('beneficiaire.class.php');
$beneficiaire = new Beneficiaire($bdd);
$id = 5;
$data = $beneficiaire->lire($id);
print_r($data);
Comment intégrer la classe dans l'application ? En principe, la modification d'une information passe par trois phases :
L'affichage de la liste des enregistrements va impliquer les opérations suivantes :
Voici d'abord l'affichage de la liste :
Puis la lecture d'un enregistrement :
Et enfin, la mise en fichier, et le réaffichage de l'information écrite :
1En fait, il est possible de stocker ce type d'informations dans la base de données, mais en utilisant des procédures stockées ou des triggers qui pourront être modifiés sans toucher à la structure des données. C'est un choix d'architecture qui est plus fréquent dans les grands systèmes.