EZPDO : Object-Relational Mapping en PHP


précédentsommairesuivant

II. Présentation d'EZPDO

EZPDO est une solution d'ORM pour PHP, distribuée sous licence BSD. Dans ce tutoriel j'ai utilisé la version 1.1.15. Il existe d'autres solutions d'ORM pour PHP, par exemple Propel (http://propel.phpdb.org/trac/Site de Propel), Doctrine (http://www.phpdoctrine.net/tracSite de doctrine) ou Metastorage. J'ai choisi de présenter EZPDO dans cet article, car c'est une solution d'ORM assez simple à mettre en place par rapport aux autres solutions. Elle nous permettra donc d'avoir rapidement un aperçu de l'ORM, même si elle souffre de quelques lacunes qui vous feront peut-être préférer une autre solution.

II-A. Installation d'EZPDO

II-A-1. Prérequis

Pour pouvoir utiliser EZPDO, il vous faudra au minimum PHP 5.0.4. Il est également nécessaire d'avoir les extensions tokenizer, xml, simplexml et sqlite. Ces extensions sont fournies par défaut dans les installations de PHP donc cela ne devrait pas poser de problèmes.

Pour vérifier que votre installation est prête à utiliser EZPDO, vous pouvez utiliser le script check.sh situé dans le répertoire scripts de votre dossier d'installation.

Vérification de compatibilité
Sélectionnez
  cd ezpdo/scripts
  php check.sh

II-A-2. Installation

L'installation d'EZPDO est très simple. Il faut simplement télécharger l'archive contenant tout ce qui est nécessaire. Elle est disponible à l'adresse http://www.ezpdo.netTéléchargement de l'archive.

Il ne vous reste plus qu'à décompresser l'archive dans un dossier accessible par votre site web et à créer un fichier de configuration.

II-A-3. Le fichier de configuration

Afin de pouvoir utiliser convenablement EZPDO il est nécessaire de créer un fichier de configuration contenant quelques informations obligatoires pour son fonctionnement.

Ce fichier peut être au format xml ou ini. Dans cet article je ne présenterai que la version xml, mais il est très simple de passer de l'une à l'autre.

Voilà un exemple de fichier config.xml minimal :

Fichier de configuration minimal
Sélectionnez
<options>
  <!-- Les fichiers contenant les classes seront recherchés dans le répertoire classes -->
  <source_dirs>classes</source_dirs>
 
  <!-- Les fichiers compilés seront enregistrés dans le dossier compiled -->
  <compiled_dir>compiled</compiled_dir>
 
  <!-- DSN par défaut -->
  <default_dsn>mysql://MonLogin:MonMdp@Serveur/BaseDeDonnées</default_dsn>
</options>

Voici les options de configuration les plus importantes. La liste détaillée de toutes les options de configuration est disponible à l'adresse http://www.ezpdo.net/blog/?p=885Options de configuration. Je vous invite à la consulter afin de connaitre toutes les options disponibles.

Option

Valeur

Défaut

Description

default_oid_column

string

-

Nom par défaut pour la colonne de la clé primaire.

default_dsn

string

-

DSN par défaut pour la base de données où les tables sont stockées. Ce DSN sera utilisé pour les classes qui n'ont pas spécifié de DSN.

source_dirs

string

-

Chemin vers le dossier contenant les fichiers de classe. Pour utiliser plusieurs dossiers, il faut les séparer par des virgules.

recursive

true, false

true

Spécifie si les dossiers contenant les fichiers de classe seront compilés récursivement.

compiled_dir

string

-

Chemin vers le dossier contenant les classes compilées. Ce dossier doit avoir un accès en écriture pour les utilisateurs de l'application.

auto_compile

true, false

true

Indique si l'auto compilation est activée. Si c'est le cas, les classes seront automatiquement compilées la première fois.

db_lib

adodb, peardb, pdo

adodb

La bibliothèque utilisée pour l'abstraction de base de données.

check_table_exists

true, false

true

Vérifie si la table existe. Il vaut mieux le désactiver en environnement de production afin d'améliorer les performances.

log_file

string

ezpdo.log

Nom du fichier de log.

Il est fortement conseillé de ne pas laisser ce fichier accessible publiquement sur votre serveur web, n'oubliez pas qu'il contient des informations sensibles.

II-B. Les premiers pas

Maintenant que la configuration a été correctement réalisée, on va pouvoir commencer à utiliser les fonctionnalités d'EZPDO :)

II-B-1. Création des classes devant contenir les données

Afin de permettre à EZPDO de connaitre la structure de notre base de données, il faut créer des classes correspondant à nos tables. Ces classes sont très simples à réaliser, elles doivent simplement avoir des attributs pour les champs de la table auquel on souhaite accéder.

Afin d'indiquer les attributs, il faut les signaler à l'aide d'un tag indiquant le type du champ dans la base de données. Ces tags sont contenus dans des commentaires et sont toujours précédés de @orm. Si l'on désire utiliser une autre base de données que celle spécifiée par défaut dans le fichier de configuration, on pourra rajouter un tag spécifiant le dns de cette base de données.
On verra plus tard qu'il est possible de rajouter d'autres tags.

Il n'est pas nécessaire de créer manuellement nos tables. Si elles n'ont pas déjà été créées, EZPDO se chargera de les créer automatiquement en rajoutant un champ o_id servant de clé primaire. Il est possible d'utiliser un autre nom pour ce champ en modifiant la valeur du paramètre default_oid_column dans le fichier de configuration, cependant il est obligatoire d'utiliser le même nom pour chacune de nos tables.

Le nombre de types gérés est assez limité, mais c'est généralement suffisant pour une utilisation courante. Pour plus d'informations concernant les types, je vous conseille de consulter la page http://www.ezpdo.net/blog/?p=6Type géré par EZPDO.

Type

Alias

Paramètres

Commentaires

bool

boolean, bit

-

Transformé en un entier de 1 octet

int(m)

integer(m)

m : Nombre de chiffres

Transformé en entier non signé

decimal(m,d)

-

m : Nombre total de chiffres, d : Chiffres après la virgule m : Nombre total de chiffres,

-

float(m)

real(m)

m : Précision

Synonyme pour les réels ANSI

char(m)

-

m : Nombre de caractères

Transformé en VARCHAR

clob(m)

text(m)

m : Nombre de caractères

Texte long

blob(m)

-

m : Nombre de caractères

Données binaires longues

date

-

-

Modifié en temps unix, int(16)

time

-

-

Modifié en temps unix, int(16)

datetime

-

-

Modifié en temps unix, int(16)


Voici un exemple de classe pour notre table Film mentionnée au début de cet article.

Film.php
Sélectionnez
/**
 * Classe pour un film
 * Ce paramètre est à utiliser uniquement si le dns est différent de celui utilisé par défaut
 * @orm MonLogin:MonMdp@Serveur/BaseDeDonnées
 */
class Film {
  /**
   * @orm char(64)
   */
  public $nom;
 
  /**
   * @orm char(256)
   */
  public $synopsis;
  /**
   * @orm boolean
   */
  public $disponible;
 
  /**
   * @orm integer
   */
  public $note;
  
   // Les autres méthodes et attributs de notre classe…
}

Il sera désormais possible d'utiliser cette classe afin d'accéder aux données contenues dans la table.
C'était simple, non ? Il suffit de créer une classe pour chacune des tables auxquelles on souhaite accéder et le tour est joué.

II-B-2. Le code source permettant d'utiliser les classes

Maintenant il ne nous reste plus qu'à accéder aux données contenues dans la table, et c'est toujours aussi simple :)

Il faut tout d'abord ajouter les fichiers nécessaires au fonctionnement d'EZPDO et récupérer une instance du gestionnaire de persistance.

Chargement des fichiers nécessaire
Sélectionnez
// Chemin d'accès vers le fichier ezpdo_runtime.php dans le dossier d'installation.
include_once('ezpdo_runtime.php');

// Le fichier de configuration. 
// Il n'est pas nécessaire de le spécifier s'il se trouve dans le répertoire courant.
epLoadConfig('config.xml');

// Récupère le gestionnaire de persistance.
$m = epManager::instance();

Maintenant voyons comment réaliser quelques opérations simples sur notre table Film.

II-B-2-a. Insertion

Lorsque l'on réalise une insertion dans la table, il n'est pas nécessaire de se soucier de nos clés primaires. EZPDO se charge de générer automatiquement la bonne valeur lors de chacune de nos insertions.

Code pour l'insertion dans la table
Sélectionnez
// Création du nouveau film
$film = $m->create('Film');

// Modification des attributs
$film->nom = 'Monkey Bone';
$film->synopsis = '...';
$film->note = 9;
$film->disponible = true;

// Insertion dans la base de données
$m->commit($film);

La méthode commit n'a pas forcément besoin d'être appelée si le paramètre auto_flush a été activé dans le fichier de configuration. Ce paramètre demande au gestionnaire de réaliser tous les enregistrements dans la base de données à la fin de l'exécution du script.

Il est également possible d'utiliser la méthode flush pour effectuer tous les traitements en attente.

 
Sélectionnez
$m->flush();

Normalement si tout s'est bien passé vous devriez avoir un nouveau film enregistré dans votre base de données.

Si une erreur se produit à ce niveau, vérifiez que le chemin d'accès vers vos classes devant contenir les données est correct.

II-B-2-b. Recherche

Imaginons que nous voulions rechercher tous les films ayant une note égale à 10 et qui sont disponibles.

Recherche d'un film
Sélectionnez
// Création d'un modèle pour les films à rechercher
$film = $m->create('Film');
$film->disponible = true;
$film->note = 10;
$film->nom = null;
$film->synopsis = null;

// On récupère la liste des films correspondant à nos critères
$listeFilm = $m->find($film)
if (!$listeFilm) {
    exit();
}
 
// Traitement sur nos résultats
foreach($listeFilm as $unFilm) {
    echo $unFilm->nom;
}

S'ils sont initialisés par le constructeur de l'objet, il est nécessaire d'affecter la valeur NULL aux attributs sur lesquels on ne souhaite pas faire de sélection. Dans le cas contraire, ces affectations ne sont pas nécessaires.

Cette méthode est simple, mais on remarque que ces requêtes sont également très limitées, il n'est par exemple pas possible de faire des recherches à partir de portions de nom. Si vous voulez le faire, il va falloir utiliser le langage de requête EZOQL que je présente un peu plus loin.

Le film qui a été créé précédemment risque potentiellement d'être inséré dans la base de données si le paramètre autoflush a été activé. Il ne faut donc utiliser cette méthode que pour des objets existant déjà dans la base de données.

Nous verrons un peu plus tard une autre méthode permettant de faire des recherches plus évoluées.

II-B-2-c. Suppression

Pour supprimer un tuple dans la table, il faut utiliser la méthode delete.

Suppression d'un tuple
Sélectionnez
$m->delete($unfilm);

Si l'on souhaite supprimer tous les tuples contenus dans la table, il suffit d'utiliser la méthode deleteAll.

Suppression de tous les tuples
Sélectionnez
$m->deleteAll('Film');

Il faut noter que lorsqu'il existe une association de composition entre deux tables, la suppression d'un tuple peut entrainer la suppression des autres tuples.

II-B-2-d. Méthodes d'accès aux données

Il existe différentes manières pour modifier et accéder aux données stockées dans les classes.

Avec les attributs
Sélectionnez
$film->nom = 'Le grand bleu';
echo $film->nom ;
Avec les getters/setters
Sélectionnez
$film->setNom('Le grand bleu');
echo $film->getNom();
Avec un tableau associatif
Sélectionnez
$film['nom'] = 'Le grand bleu';
echo $film['nom'];

Ces trois bouts de code effectuent exactement le même traitement.

II-C. Pour aller un peu plus loin

Jusqu'à présent tout cela était faisable assez facilement avec des requêtes SQL standards, voyons maintenant comment l'ORM va réellement nous faciliter le développement de nos applications web.

II-C-1. Gérer les associations entre les tables

Les associations entre les tables sont l'avantage principal des bases de données relationnelles. Pour matérialiser une association n-n entre deux tables, il faut créer une table de jointure. Pour retrouver les données liées entre les deux tables, il faut alors réaliser des requêtes parfois complexes utilisant les jointures entre les tables. Même si les relations 1-n sont généralement plus simples à gérer cela peut parfois se révéler contraignant, par exemple en ce qui concerne les relations de composition.
Voyons maintenant comment réaliser une association entre deux tables grâce à EZPDO, et vous verrez comment cela peut simplifier votre code.

L'association entre deux tables se matérialise en rajoutant des informations sur les classes décrivant nos tables. On a vu que l'on utilisait des tags pour décrire les données contenues dans nos tables dans ces classes. Pour décrire nos relations, on utilisera également des tags.

La syntaxe générale d'un tag décrivant une association est la suivante :

Syntaxe pour déclarer une association
Sélectionnez
@orm [has|composed_of] [one|many] NomClasse [inverse[(var)]]

Voyons un peu plus en détail chacun de ces mots-clés.

II-C-1-a. Composition et agrégation (has / composed_of)

Les mots-clés has et composed_of sont utilisés pour spécifier le type de relation entre les tables, respectivement l'agrégation et la composition.

Lorsque l'on utilise une agrégation, si un objet A est lié à un ou plusieurs objets B, lorsque l'on détruira l'objet A, aucun objet B ne sera détruit. Si par contre on utilise une composition pour lier les deux objets, EZPDO se chargera de détruire automatiquement tous les objets B liés par cette relation.

II-C-1-b. Cardinalité des relations (one/many)

Les mots-clés one et many permettent de matérialiser les cardinalités des relations. one symbolise le fait qu'un tuple ne possède qu'une seule référence vers un tuple d'une autre table, many au contraire indique que le tuple pourra être lié à plusieurs tuples de l'autre table.

Reprenons notre base de données d'exemple. Pour matérialiser le fait qu'un film a un et un seul réalisateur et qu'un réalisateur peut réaliser plusieurs films, il faut rajouter les lignes suivantes :

Relation entre Film et Réalisateur
Sélectionnez
class Film {
   /**
   * @orm has one Réalisateur
   */  
  public $realisateur;
}

class Réalisateur {
   /**
   * @orm composed_of many Film
   */
   public $film;
}

Dans notre cas le mot-clé one n'était pas nécessaire puisqu'il est utilisé par défaut lorsqu'aucun mot-clé n'est spécifié.

On peut également noter l'utilisation de composed_of pour l'attribut film de la classe Réalisateur. On indique ainsi que lorsque l'on supprimera un réalisateur tous les films qu'il aura créés seront également supprimés.

Nous venons de matérialiser une relation 1-n entre les deux tables. Voyons maintenant comment matérialiser le fait qu'un film ait plusieurs acteurs et qu'un acteur puisse jouer dans plusieurs films.

Association entre Film et Acteur
Sélectionnez
class Film {
   /**
   * @orm has many Acteur
   */  
  public $acteur;
}

class Acteur {
   /**
   * @orm has many Film
   */
   public $film;
}

Voilà notre relation n-n modélisée. C'est très simple à faire pourtant grâce à ces quelques lignes, EZPDO pourra automatiquement créer une table servant de jointure entre les deux tables. On peut noter qu'EZPDO permet également de modéliser des relations réflexives.

II-C-1-c. Utiliser les associations entre les tables

Maintenant que nous savons décrire nos associations, voyons comment associer facilement deux tuples ensemble et retrouver très facilement les tables liées les unes aux autres.

Tout d'abord pour associer à un tuple, un ou plusieurs tuples on utilise une affectation standard pour l'attribut matérialisant la relation. Si l'on souhaite associer plusieurs tuples à la fois on peut utiliser un tableau.

Créer des relations entre nos tuples
Sélectionnez
$film1 = $m->create('Film');
$film2 = $m->create('Film');
... // Initialisation des films

$acteur1 = $m->create('Acteur');
$acteur2 = $m->create('Acteur');
$acteur3 = $m->create('Acteur');
... // Initialisation des acteurs

$realisateur = $m->create('Realisateur');
... // Initialisation du réalisateur

// 
$film1->acteurs = array($acteur1, $acteur2);
$film2->acteurs = $acteur3;

$realisateur->films = $film1;
$film2->realisateur = $realisateur;

Dans notre exemple nous n'avons matérialisé des liens que dans un seul sens, il sera possible à partir d'un film de retrouver ses acteurs, mais pas dans le sens inverse. Si l'on souhaite le faire (ce qui sera probablement le cas) il y a deux solutions. Soit affecter également à chacun des acteurs les films, soit utiliser un nouveau tag pour notre attribut. Il s'agit du tag inverse qui prend en paramètre le nom de l'attribut dans notre classe qui contiendra notre association. Voilà comment il faudra modifier nos classes Film et Acteur

Créer des relations entre nos tuples
Sélectionnez
class Film {
   /**
   * @orm has many Acteur inverse(acteur)
   */  
  public $acteur;
}

class Acteur {
   /**
   * @orm has many Film inverse(film)
   */
   public $film;
}

Cette solution est très simple à mettre en place et simplifiera encore un peu le code.

Une fois que cela est fait, on risque sûrement à un moment d'avoir besoin de retrouver les tuples associés entre les tables. Comme précédemment cela se fait très facilement, en récupérant les valeurs des attributs.
Dans le cas d'une relation multiple, on pourra récupérer une collection contenant la liste des tuples associés.
L'attribut matérialisant notre relation contiendra les objets de nos tables. Il sera possible de les manipuler exactement comme n'importe quel autre objet, et par exemple de faire des mises à jour.
Voilà un court exemple d'utilisation :

Parcourir nos associations
Sélectionnez
// On souhaite afficher le nom de tous les films du réalisateur de ben-hur
foreach($benhur->realisateur->film as $unFilm) {
    echo $unFilm['nom'];
}

// Maintenant on souhaite renommer le réalisateur de Léon en Bob Morane
$leon->realisateur->nom = 'Bob';
$leon->realisateur->prenom = 'Morane';

Et voilà, aucune requête SQL pourtant en quelques lignes vous avez réalisé plusieurs jointures et des mises à jour.

En guise de conclusion sur les associations, je pense qu'il faut retenir que l'intérêt principal de l'ORM dans la gestion des associations est qu'il permet de les gérer comme de simples attributs de nos objets. Il n'est plus nécessaire d'effectuer des requêtes compliquées de mise à jour et de sélection pour retrouver nos valeurs.

II-C-2. Le langage de requête EZOQL

Pour pouvoir réaliser des recherches un peu plus complexes que ce que nous avons vu précédemment, il est nécessaire d'utiliser un langage de requête un peu plus évolué. Ce langage devrait être très simple pour ceux qui connaissent déjà le SQL.

Il est important de savoir que même si la syntaxe ressemble fortement à celle de SQL, les traitements effectués ne seront pas forcément les mêmes que pour une requête SQL similaire.

II-C-2-a. Recherches avec critères

Pour effectuer une requête, on utilise la méthode find du manager. Cette méthode nous renverra une collection contenant tous les tuples de nos tables correspondant à notre requête. Il est bien sûr possible d'avoir des critères lors des recherches.

Recherche avec critère
Sélectionnez
// Recherche les réalisateurs dont le prénom est Bob
$realisateurs = $m->find("from Réalisateur where prénom = 'Bob'");

// Recherche tous les films contenant 'bleu' dans le nom
$films = $m->find("from Film as f where f.nom like '%bleu%'");

// Recherche les acteurs dont la date de naissance n'a pas été spécifiée.
$acteurs = $m->find("from Acteur where dateDeNaissance is null");

On remarque dans la deuxième requête l'utilisation du mot-clé as pour créer un alias de la table.

On peut également utiliser des paramètres dans les critères de recherche. Pour cela il faut remplacer la valeur par ? dans la requête et mettre la liste de nos valeurs à la suite.

Recherche avec paramètres
Sélectionnez
// Affiche les acteurs nés après mon père et avant moi.
$acteurs = $m->find("from Acteur where dateDeNaissance < ? and dateDeNaissance  > ?", monAge, ageDeMonPere);

II-C-2-b. Fonctions de tri et de limitation

On peut trier par ordre croissant, décroissant et aléatoire. Si aucun ordre de tri n'est spécifié, ce sera l'ordre croissant qui sera utilisé par défaut.

Critères de tri pour le résultat
Sélectionnez
// Affiche tous les films par ordre alphabétique
$films = $m->find("from Film order by Film.nom asc");

// Affiche les acteurs aléatoirement
$acteurs = $m->find("from Acteur where order by random()");

Il est possible également d'avoir plusieurs critères de tri dans une même requête.

Plusieurs critères de tri dans une même requête
Sélectionnez
// Affiche les réalisateurs triés par prénom croissant et par nom décroissant.
$acteurs = $m->find("from Acteur as a where order a.prénom, a.nom desc");

Il est enfin possible de limiter le nombre de résultats renvoyés par une requête.

Limitation du nombre de résultats renvoyés
Sélectionnez
// Affiche les cinq premiers réalisateurs
$realisateurs = $m->find("from Réalisateur order by nom  limit 0, 5");

II-C-2-c. Fonctions d'agrégation

Les fonctions d'agrégation disponibles sont les suivantes :

Fonction

Résultat

AVG

Renvoie la moyenne arithmétique

COUNT

Nombre de tuples retournés

MAX

Valeur maximale

MIN

Valeur minimale

SUM

La somme de toutes les valeurs numériques

Voilà un exemple d'utilisation de la fonction AVG

Utilisation de la fonction AVG
Sélectionnez
$noteMoyenne = $m.find("AVG(note) FROM Film");

Il est bien sûr possible de combiner toutes les fonctions que nous venons de voir dans EZOQL pour produire des requêtes relativement évoluées.

II-C-3. Les autres fonctionnalités

EZPDO propose d'autres fonctionnalités dont je ne traiterai pas dans cet article de présentation à l'ORM en PHP. Ces fonctionnalités sont entre autres la gestion des transactions, les écouteurs sur événements et l'héritage. Si vous voulez plus de renseignements, je vous invite à consulter la documentation contenue sur le site web.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2007 Pierre-Nicolas Mougel. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.