Академический Документы
Профессиональный Документы
Культура Документы
Par Cybermanu
www.siteduzero.com
2/67
Sommaire
Sommaire ........................................................................................................................................... 2 Partager .............................................................................................................................................. 2 Apprendre utiliser Doctrine .............................................................................................................. 4
Connaissances requises ................................................................................................................................................... 4 Remerciements ................................................................................................................................................................. 4
www.siteduzero.com
Sommaire
3/67
Timestampable .......................................................................................................................................................................................................... 43 SoftDelete .................................................................................................................................................................................................................. 45 Versionable ................................................................................................................................................................................................................ 46 Sluggable .................................................................................................................................................................................................................. 47 Structure d'un template ............................................................................................................................................................................................. 47 Comment a marche ? .............................................................................................................................................................................................. 47 Mthodes dlgues ................................................................................................................................................................................................. 48
www.siteduzero.com
Sommaire
4/67
Par
Cybermanu
Mise jour : 15/02/2011 Difficult : Intermdiaire 296 visites depuis 7 jours, class 348/797 Bienvenue tous dans ce tutoriel. Je vais vous prsenter ici un ORM pour PHP : Doctrine. Si vous ne savez pas ce qu'est un ORM, a tombe bien, je vous explique tout cela juste aprs.
Connaissances requises
Pour suivre ce tuto, il est ncessaire : de bien connatre PHP et la POO ; de savoir comment fonctionnent les relations dans une base de donnes (au moins basiquement) ; d'avoir un serveur avec PHP 5.2.3 minimum ; et... d'avoir du temps libre ! En effet, Doctrine est vraiment TRS complet, et on ne pourra de toute faon pas tout voir dans ce tutoriel. Si vous ne connaissez pas la POO, arrtez-vous l ! Je vous conseille d'aller faire un tour sur le tuto de vyk12. Revenez juste aprs, vous verrez, a vaut le coup. Je vous recommande galement de savoir, en gros, ce qu'est l'architecture MVC. Si vous ne la connaissez pas, ce n'est pas dramatique, vous comprendrez comment on organise notre projet au fur et mesure du tutoriel.
Remerciements
Avant de commencer, je souhaite remercier les personnes qui ont contribu ce tuto, m'ont donn des ides, des conseils, notamment Nami Doc.
La version que j'utiliserai et expliquerai dans ce tutoriel est la version 1.2. Il est possible que je mette jour le tutoriel lorsque la version 2 sortira en version stable.
www.siteduzero.com
5/67
Conventions utilises
Dans les exemples que je donnerai tout au long du tutoriel, j'emploierai souvent certaines variables spciales : $manager : il reprsente une instance de Doctrine_Manager et peut tre rcupr avec Doctrine_Manager::getInstance() ; $connexion, $connection ou $conn : il s'agit d'une instance de Doctrine_Connection , qui peut tre obtenue avec Doctrine_Manager::connection() ; $record : c'est un objet hritant de Doctrine_Record , faisant partie du modle ; $table : il s'agit d'une instance d'une classe fille de Doctrine_Table, reprsentant l'une des tables de la base de donnes. Elle peut tre rcupre avec Doctrine_Core::getTable() ou $record->getTable() . Rassurez-vous : vous ne connaissez pas du tout ces classes pour l'instant et c'est normal, nous les tudierons dans le tutoriel.
Prsentation et installation
Tout d'abord, nous allons voir ce qu'est Doctrine, puis nous l'installerons, pour pouvoir commencer coder un peu, quand mme. Attention, je vous prviens, je ne serai pas aussi direct que dans le tuto de christophetd. Je passerai plus de temps vous faire dcouvrir le fonctionnement interne de Doctrine. Si vous voulez commencer coder tout de suite, allez voir le sien, il est relativement bien fait pour commencer rapidement. Si vous souhaitez en savoir un peu plus, et apprendre bien utiliser Doctrine et pouvoir l'tendre et le personnaliser ensuite (behaviors, et tout a ! ), restez ici.
Un ORM, c'est en quelque sorte une interface entre votre base de donnes et vous. Il s'agit d'un ensemble de classes (ou bibliothques ) qui fonctionne de manire indpendante. Le but est de nous permettre d'accder au contenu de la base de donnes avec un langage orient objet (PHP en l'occurrence). Pour ceux qui ont dj entendu parler de Symfony, sachez que Doctrine est dornavant l'ORM intgr par dfaut.
Pour comprendre concrtement comment Doctrine fonctionne, voyons un exemple. Imaginons que l'on possde une table contenant des articles, avec un titre, un contenu et une certaine catgorie. On pourra y accder avec un objet Article en PHP : Code : PHP <?php $article = new Article(); $article->title = 'Tutoriel Doctrine'; $article->content = 'blablabla...'; $article->categorie = $monObjetCategorie; $article->save();
www.siteduzero.com
6/67
Et c'est l'un des gros avantages d'un ORM : si vous dplacez votre projet sur un autre serveur, avec un autre SGBD, vous n'aurez pas vous soucier des diffrences qu'il peut y avoir entre les langages. En effet, Doctrine repose sur PDO et supporte les mmes drivers. V ous n'aurez pas non plus crire les classes (par exemple Categorie ou Article) vous-mme, elles sont gnres automatiquement. Ce qui ne veut pas dire qu'on ne pourra pas les personnaliser, bien au contraire. V oici un schma pour comprendre rapidement comment Doctrine s'articule :
Doctrine est constitu de deux couches distinctes : Je vous l'ai dit, l'une repose sur PDO, laquelle elle ajoute des fonctionnalits, c'est ce qui s'appelle la DBAL : Database Abstraction Layer. Elle pourrait s'utiliser directement, sans la couche ORM. Son but, comme son nom l'indique, est de fournir une couche d'abstraction, c'est--dire que grce elle, on ne se soucie plus de comment fonctionne notre base de donnes. ce niveau-l, on n'utilise pas encore de langage-objet. La deuxime couche (ORM, donc) permet de faire le lien entre nos objets PHP et la DBAL, c'est ce qui nous fournit une interface oriente objet pour manipuler la base de donnes. Elle interagit avec la DBAL et nous retourne des rsultats sous forme d'objets. Ne faites pas de confusion avec PDO : j'ai parl d'abstraction dans le schma, mais PDO ne propose pas d'abstraction de la base de donnes. Elle fournit simplement des fonctions communes, peu importe le SGBD, mais il faut quand mme se plier au langage local . Bon assez bavard, je suppose que vous tes tous impatients de l'installer ! Comment a, non ?
Installation Tlchargement
Ici, je vais seulement vous montrer la manire la plus simple et accessible tout le monde pour installer Doctrine. Commencez par tlcharger le package .tgz ici : http://www.doctrine-project.org/projects/orm/download. La dernire version stable l'heure o j'cris ces lignes est la 1.2.2 . Pour extraire l'archive, utilisez un logiciel comme 7zip ou Winrar. Si vous souhaitez l'installer via SVN ou PEAR, je vous renvoie la documentation en ligne (in english ). Mais nous ne nous attarderons pas l-dessus. Notez tout de mme qu'il existe un tuto sur SVN sur le Site du Zro.
la racine de votre projet, crez un rpertoire ~/lib/. Nous placerons ici toutes nos classes. Nos classes de modles seront places dans ~/lib/models/.
www.siteduzero.com
7/67
Profitons-en pour crer aussi un rpertoire ~/web/ la racine. Tous les fichiers destins tre vus par le visiteur seront placs ici. J'insiste sur le fait que la racine Web est ~/web/. C'est une bonne habitude prendre de ne pas laisser vos sources librement accessibles. Ne mettez dans ce dossier Web que le strict ncessaire, notamment vos images, feuilles de style, javascript, etc.
Concernant la base de donnes, il est prfrable (et je vous l'ordonne ! ) de ddier compltement une base de donnes Doctrine. Cela permettra de laisser Doctrine s'occuper de toute la configuration. Si vous ne lui ddiez pas de base de donnes, il pourrait y avoir des interactions non dsires avec vos autres projets. Nous enregistrons aussi l'autoloader de Doctrine avec spl_autoload_register(). C'est celui-ci qui va s'occuper d'inclure les fichiers contenant les classes pour nous. Waaaa, c'est gnial ! Comment a marche ?
En fait, c'est assez simple. Pour commencer, l'autoloader (Doctrine_Core::autoload()) sera appel par PHP chaque fois qu'une classe non dfinie sera utilise, avec en paramtre le nom de cette classe. C'est cette fonction de se dbrouiller ensuite pour faire les include() ou require() ncessaires. Pour trouver o sont rangs ces fichiers, une convention de Doctrine est que le nom des classes indique aussi o elles sont situes : il suffit de remplacer les _ par des / et on obtient le chemin du fichier (relatif par rapport au dossier contenant Doctrine). Par exemple, la classe Doctrine_Record_Abstract est situe dans ~/lib/vendor/doctrine/Doctrine/Record/Abstract.php. noter que Doctrine ne nomme pas les fichiers contenant des classes avec l'extension .class.php. Cependant, il y a une exception pour les fichiers contenant nos modles, et Doctrine les inclura toujours (~/lib/models/). Si vous tes curieux et avez ouvert le fichier Doctrine.php, vous avez d vous apercevoir que la classe Doctrine est vide ! En fait, elle hrite de Doctrine_Core. Je vous recommande d'utiliser Doctrine_Core par la suite, Doctrine.php tant l uniquement pour la rtro-compatibilit.
Avant de conclure, je vais faire un petit point sur l'organisation du projet que nous allons utiliser pour suivre ce tutoriel. Je vous ai brivement parl de MVC en introduction. Cependant, pour ce tutoriel, il ne sera pas toujours ncessaire de le
www.siteduzero.com
8/67
respecter, et pour ne pas compliquer la comprhension de ceux qui ne connaissent pas cette architecture, il m'arrivera parfois de ne pas suivre le pattern MVC la lettre. Je vous conseille la lecture de l'un des tutoriels prsents sur ce site, par exemple celui de Savageman, si voulez en savoir plus sur le sujet. V ous tes bien sr libres d'intgrer directement votre projet les exemples que je vous donnerai. Avant d'entrer dans le vif du sujet, crez le fichier ~/web/index.php et placez-y les lignes suivantes pour vrifier que Doctrine est bien install : Code : PHP - ~/web/index.php <?php require_once(dirname(__FILE__).'/../config/global.php'); echo Doctrine_Core::getPath();
Rappelez-vous bien que le fichier global.php devra tre inclus dans tous vos fichiers !
V oil voil, le projet est maintenant oprationnel. Dans la partie suivante nous allons pouvoir nous connecter la base de donnes.
www.siteduzero.com
9/67
Je m'attarde un peu l-dessus, parce que c'est le principe du singleton : on ne peut pas instancier des objets comme on veut. On va donc utiliser une mthode statique : Doctrine_Manager::getInstance(). C'est elle qui va nous retourner un nouvel objet ; et l'intrt est qu'elle nous retournera toujours le mme, au lieu d'en crer un chaque fois. Nous n'avons en effet pas besoin d'utiliser plusieurs objets Doctrine_Manager. Doctrine_Manager nous permet de configurer de nombreuses options, mais nous reviendrons dessus au fur et mesure du tutoriel. Il y a aussi une partie en annexe qui rcapitule tout a.
Note : Doctrine_Manager peut prendre en charge plusieurs connexions diffrentes bases de donnes. Il est conseill pour cela d'affecter explicitement un label aux connexions cres : Doctrine_Manager::connection('dsn', 'label');. Il est possible ensuite de les rcuprer avec Doctrine_Manager::getConnection('label');.
www.siteduzero.com
10/67
Le langage Yaml
Ce langage est un langage de description, un peu comme le XML, mais moins lourd. Pour info, YAML est un acronyme rcursif, tout comme PHP, et signifie YAML Ain't Markup Language. Comme vous allez le voir, la syntaxe du Yaml repose presque uniquement sur l'indentation du code. Attention, quelque chose de trs important : l'indentation doit se faire UNIQUEMENT AVEC DES ESPACES , jamais avec des tabulations. Le langage repose sur ces rgles principales : Les nombres (entiers, flottants, etc.) sont crits naturellement. La valeur Null peut tre indique avec null ou ~. Les boolens sont exprims par true ou false. Les dates sont crites au format ISO-8601, c'est--dire, par exemple, 2010-06-03 23:30:53. Les chanes de caractres peuvent tre crites sans dlimiteurs, dlimites par des simple quotes ('), ou par des doubles quotes ("). Lorsque la chane s'tend sur plusieurs lignes, il faut utiliser le symbole pipe | pour l'indiquer. Les paires cl/valeur sont spares par ':'. Plutt que l'indentation, on peut utiliser les symboles [] et {} pour indiquer explicitement un tableau, par exemple, tableau: { cle: valeur, cle2: valeur2 }. Les accolades indiquent des paires cl/valeur, les crochets indiquent simplement des valeurs. Par exemple : tableau: [valeur1, valeur2, valeur3]. Les commentaires sont indiqus par #. Toute ligne contenant un # devient commentaire sur le reste de la ligne.
Le schma
V oyons tout de suite le schma que j'utiliserai dans la suite de ce tutoriel. Nous placerons tout ceci dans le fichier ~/config/schema.yml : Code : Autre - ~/config/schema.yml # Cela reprsentera nos articles : Article: columns: title: type: string(255) notnull: true content: type: string notnull: true user_id: type: integer relations: User: class: User local: user_id foreign: id alias: User # Inutile, puisque l'alias par dfaut est le nom de la classe. foreignAlias: Articles # Et l les utilisateurs : User: tableName: member columns: login: type: string(255) unique: true notnull: true password: type: string(255) notnull: true
www.siteduzero.com
11/67
Dtaillons maintenant le schma ci-dessus. Les cls de premier niveau reprsentent nos classes (Article et User). Chaque classe sera reprsente par une table dans la base de donnes. Doctrine dduit automatiquement le nom de la table, si on ne spcifie pas l'option tableName. l'intrieur, on retrouve les cls columns (qui contient la dfinition des colonnes de la table) et relations (qui dfinit les relations entre les tables).
Les colonnes
Chaque colonne accepte plusieurs options, dont : type : un des types suivants : boolean, integer, float, decimal, string, blob, clob, timestamp, time, date, enu m, gzip, array, object. Doctrine adapte ensuite le type utilis dans la base de donnes selon ce que le SGBD supporte ; notnull : indique si le champ peut tre vide ou s'il doit toujours contenir une valeur (true ou false) ; unique : indique si le champ doit tre unique ou non (true ou false). V ous avez d remarquer que l'on peut indiquer comme type array ou object ; or, ces types n'existent pas dans un SGBD classique. En fait, Doctrine va s'occuper de srialiser/dsrialiser automatiquement la valeur de ces champs. Ils sont utiles lorsque l'on veut viter de crer une table supplmentaire avec des relations, parfois un peu lourdes grer pour pas grand-chose.
Les relations
Il y a plusieurs manires de dfinir les relations. Ici, nous sommes partis de l'objet Article et avons indiqu qu'ils ont un auteur. Nous aurions aussi pu indiquer cette relation partir de l'objet User et indiquer que chacun peut possder plusieurs articles. Nous commenons par donner un nom notre relation ('User'). La classe trangre est indique par class. L'identifiant local est spcifi par local, et l'identifiant tranger par foreign. Doctrine nous donne la possibilit de donner des alias avec alias et foreignAlias. Ceci nous permettra d'utiliser ensuite : <?php $article->AliasPourUser->login.
Le langage Yaml n'est en fait qu'une manire alternative de dfinir notre modle. Doctrine le traduit ensuite en PHP ; mais nous pouvons bien entendu crire directement tout cela en PHP.
V oyons l'quivalent du schma prcdent : Code : PHP - ~/lib/models/generated/BaseArticle.php <?php class BaseArticle extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('title', 'string', 255, array('notnull' => true)); $this->hasColumn('content', 'string', null, array('notnull' => true)); $this->hasColumn('user_id', 'integer', null, array()); } public function setUp() { $this->hasOne('User as User', array('local' => 'user_id', 'foreign' => 'id')); // Mme remarque que pour l'alias ici : il s'agit par dfaut
www.siteduzero.com
12/67
Code : PHP - ~/lib/models/generated/BaseUser.php <?php class BaseUser extends Doctrine_Record { public function setTableDefinition() { $this->setTableName('member'); $this->hasColumn('login', 'string', 255, array('unique' => true, 'notnull' => true)); $this->hasColumn('password', 'string', 255, array('notnull' => true)); } public function setUp() { $this->hasMany('Article as Articles', array('local' => 'id', 'foreign' => 'user_id')); } }
La mthode hasColumn() permet de dfinir les colonnes ; elle prend en argument le nom, le type, la longueur, et les options supplmentaires. Les mthodes hasOne() / hasMany() permettent de dfinir les relations ; avec en argument le nom du modle et ventuellement un alias, et les options.
Lorsque nous demanderons Doctrine de les gnrer automatiquement partir du Yaml, Doctrine aura besoin de recrer ces fichiers. Pour ne pas effacer ceux qui nous servent (et que l'on modifiera ensuite), Doctrine gnre une classe intermdiaire , que nous NE MODIFIERONS PAS. Nous modifierons seulement les classes Article et User (qui hritent donc de BaseArticle et BaseUser ). Si vous ne comprenez pas tout de suite, ne vous inquitez pas, je rcapitulerai juste aprs.
www.siteduzero.com
13/67
V ous noterez que nos modles hritent de Doctrine_Record et que nos tables hritent de Doctrine_Table. Ceci est OBLIGATOIRE ! Les mthodes setTableName(), hasColumn(), hasMany() etc... sont hrites de Doctrine_Record. Allez donc jeter un coup d'oeil au fichier ~/lib/vendor/doctrine/Doctrine/Record.php.
require_once(LIB_DIR.'vendor/doctrine/Doctrine.php'); spl_autoload_register(array('Doctrine_Core', 'autoload')); spl_autoload_register(array('Doctrine_Core', 'modelsAutoload')); $manager = Doctrine_Manager::getInstance(); $conn = Doctrine_Manager::connection(CFG_DB_DSN); $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL); $manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true); $manager->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true); $manager->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_CONSERVATIVE); Doctrine_Core::loadModels(LIB_DIR.'models/');
IMPORTANT Nous ajoutons un autre autoloader (Doctrine_Core::modelsAutoload). Celui-ci se chargera d'inclure les classes de modles. En effet, l'autoloader basique ne trouve les fichiers que par leur nom (souvenez-vous, je l'ai expliqu en premire partie du tuto). Or, les fichiers contenant les modles ne respectent pas cette rgle : souvenezvous lorsque nous avons dfini le schma. Je vous avais parl des classes BaseXXX : elles sont places par Doctrine dans un dossier generated/.
Nous utilisons la mthode setAttribute() de Doctrine_Manager pour personnaliser plusieurs attributs. Je vous invite vous reporter la partie Annexes pour une explication dtaille de ces attributs. Sachez simplement que nous dfinissions le ncessaire pour une utilisation classique, et pour le chargement automatique de tous les fichiers. La dernire instruction <?php Doctrine_Core::loadModels(LIB_DIR.'models/') demande d'inclure les modles se situant dans le dossier indiqu. Pour le code des modles, je vous laisse vous reporter aux chapitres prcdents. V oici les commandes excuter pour que Doctrine gnre les fichiers de modle et construise notre base de donnes :
www.siteduzero.com
14/67
ATTENTION : Si vous n'avez pas ddi une base de donnes entire Doctrine comme je vous l'ai conseill lors de l'installation, n'excutez pas Doctrine_Core::dropDatabases() ! Cela efface la base de donnes avant de la reconstruire. Attention donc aux autres projets partageant la mme base de donnes ! Si vous partagez la base de donnes avec d'autres applications, vous pouvez simplement supprimer les tables utilises par Doctrine la main, ce qui peut vite devenir fastidieux, je vous l'accorde...
Code : PHP - ~/web/builder.php <?php require(dirname(__FILE__).'/../config/global.php'); // Si elle existe, supprimez la base existante. // Attention, cela vide totalement la base de donnes ! Doctrine_Core::dropDatabases(); // Cration de la base (uniquement si elle n'EXISTE PAS) Doctrine_Core::createDatabases(); // Cration des fichiers de modle partir du schema.yml // Si vous n'utilisez pas le Yaml, n'excutez pas cette ligne ! Doctrine_Core::generateModelsFromYaml(CFG_DIR.'schema.yml', LIB_DIR.'models', array('generateTableClasses' => true)); // Cration des tables Doctrine_Core::createTablesFromModels(LIB_DIR.'models');
Lorsque vous appellerez le fichier builder.php, votre base de donnes et les fichiers des classes BaseXXX seront reconstruits (les autres seront crs s'ils n'existent pas ; s'ils existent dj, ils ne seront pas modifis). Tout ce fonctionnement automatique peut vous paratre un peu obscur. Excutez donc le fichier ~/web/builder.php. Je vous laisse maintenant aller jeter un coup d'oeil dans votre base de donnes et dans le rpertoire ~/lib/models/. Un dossier generated/ y a t cr, ainsi que les classes BaseXXX . Notez que les classes normales (c'est--dire Article, User, ArticleTable et UserTable) ont t cres parce qu'elles n'existaient pas. Par la suite, si vous rgnrez nouveau tout cela, elles ne seront plus modifies. C'est donc dans celles-ci que nous travaillerons. Pour finir, notez que j'ai indiqu ici (en Yaml et en PHP) les options les plus utilises, mais il en existe d'autres et je vous les montrerai au cours du tutoriel. Rappelez-moi comment vous faisiez jusqu'ici ? V ous construisiez vos bases de donnes la main ? Eh bien maintenant vous pouvez oublier tout a !
(Oui enfin, ne prenez pas non plus la lettre tout ce que je peux raconter... )
www.siteduzero.com
15/67
Doctrine nous fournit plusieurs manires d'accder aux proprits de nos objets : Code : PHP <?php $article->title = 'Mon titre'; $article['title'] = 'Mon titre'; $article->set('title', 'Mon titre'); $article->content = "Contenu de l'article..."; $article['content'] = "Contenu de l'article..."; // etc. // Et on les rcupre de la mme manire : echo $article->title;
Rien que a ! Personnellement, j'aime bien utiliser la notation $obj->title, mais vous faites bien sr comme vous voulez. Si vous utilisez Doctrine par l'intermdiaire de symfony , notez que ce dernier ajoute la possibilit d'utiliser des getters et setters (des mthodes commenant par get ou set permettant de rcuprer/modifier les attributs) ; par exemple, <?php $obj->setTitle('mon titre') ou <?php echo $obj->getContent().
www.siteduzero.com
16/67
<?php // Cration d'un nouvel utilisateur : $author = new User(); $author->login = 'Grard'; $author->password = 'pass'; // Affectation de cet utilisateur notre article : $article->user_id = $author->id; // Ou bien directement : $article->User = $author;
Comme vous le voyez, on peut directement affecter l'auteur, sans indiquer explicitement son id, mme si c'est cette valeur qui est stocke dans la base de donnes. Doctrine s'occupe de tout cela automatiquement pour nous simplifier la tche. Pour terminer, on sauvegarde tous les changements effectus sur notre article : Code : PHP <?php $article->save();
Rcuprer un objet
C'est l que les classes reprsentant les tables vont nous tre utiles. Doctrine nous fournit plusieurs finders magiques : Code : PHP <?php $table = Doctrine_Core::getTable('Article'); // Rcuprer un article avec son id, par exemple $id = 1 $article = $table->find($id); // Rcuprer un article avec une autre proprit $article = $table->findOneByTitle('Mon titre'); // Avec deux proprits $article = $table->findOneByTitleAndContent('Mon titre', "Contenu de l'article..."); // Avec une relation $article = $table->findOneByUser($user);
Comment a marche ? Je n'ai jamais cr ces mthodes moi ! C'est Doctrine_Table qui les dfinit ?
Haha, mystre...
Magie, magie...
Pour rpondre la question, et bien... oui et non.
www.siteduzero.com
17/67
Par exemple, en appelant findOneByTitleAndContent("Mon titre", "Contenu de l'article..."), Doctrine va se dire Tiens, il veut que je lui donne un article dont le titre est "Mon titre" et le contenu est "Contenu de l'article"... , et excuter une requte en consquence. Bon, en ralit, c'est un peu moins magique que a, et je vous invite aller voir le dtail de cette fonction dans le code de Doctrine (essayez de la retrouver ). Pendant qu'on y est, parlons des attributs de nos objets (title, content et user_id ). En fait, ils ne sont pas dfinis en dur en tant qu'attributs. Pour faire simple, c'est comme s'ils taient stocks dans un tableau. Doctrine_Record implmente la mthode magique __set(), qui est appele automatiquement lorsque l'on essaye d'utiliser un attribut qui n'existe pas. Ensuite, c'est comme pour __call : c'est ici que l'attribut est rcupr. Et pour ce qui est de $article['title'] = '...', et bien Doctrine_Record implmente l'interface ArrayAccess (dont le nom est assez explicite ). Je vous encourage par la mme occasion vous documenter au sujet de cette interface. Elle n'est pas extrmement complique comprendre, et beaucoup de composants de Doctrine l'utilisent. J'espre que maintenant tout ce fonctionnement magique vous paratra un peu moins obscur.
Il est bien entendu aussi possible de crer/modifier des collections : Code : PHP <?php $articles = new Doctrine_Collection('Article'); // Ou bien $articles = $table->findAll(); $articles[0] = new Article(); $articles[1] = new Article(); $articles[0]->title = "Titre de l'article n1"; $articles[0]->content = 'Contenu 1'; $articles[1]->title = "Titre de l'article n2"; // etc.
www.siteduzero.com
18/67
Je vous ai dit que beaucoup de composants de Doctrine implmentaient l'interface ArrayAccess. Et bien comme vous le voyez dans le code ci-dessus, c'est le cas de Doctrine_Collection . Notez aussi que pour pouvoir tre parcouru avec foreach , il faut aussi implmenter une interface iterator (IteratorAggregate en l'occurrence).
Pour sauvegarder tous nos objets, il existe un raccourci : nous ne serons pas obligs de sauvegarder nos objets un par un (ouf ! ). Doctrine_Collection possde tout simplement une mthode save() qui appelle cette mme mthode sur tous ses objets. Code : PHP <?php $articles->save();
Le langage DQL
Nous allons ici explorer un peu plus en profondeur Doctrine et dcouvrir son langage interne : le DQL. DQL signifie Doctrine Query Language. L'intrt est encore une fois de ne pas se soucier du type de base de donnes utilis. En effet, mme s'ils utilisent le langage SQL, beaucoup de SGBD (tous ? ) implmentent leurs propres fonctions et spcificits dans le langage. Doctrine se chargera de traduire le DQL dans le langage appropri.
Doctrine va gnrer des instructions en SQL. Mais nous avons la possibilit d'crire nous-mmes les requtes la main. Pour cela, nous allons utiliser des objets Doctrine_Query. Code : PHP <?php $q = Doctrine_Query::create() ->from('Article a') ->leftJoin('a.User u') ->where('a.id = ?', 1); $article = $q->fetchOne();
Cet exemple produit le mme effet que prcdemment, le SQL gnr tant le mme ; mais la diffrence prs que nous avons construit la requte la main. Rassurez-vous, nous allons dtailler tout cela juste aprs.
www.siteduzero.com
19/67
Oui, bon, j'avoue, sur cet exemple, l'intrt n'est pas flagrant. Mais ds que vous allez vouloir faire des slections plus complexes qu'avec les finders (souvenez-vous, les mthodes $table->findByXXX()), cela va devenir indispensable. Imaginons que nos articles possdent un champ publishable dont le contenu indique si l'article est publiable ou non. Pour rcuprer un article, il nous faut alors prendre ceci en compte et filtrer les rsultats : Code : PHP <?php $q = Doctrine_Query::create() ->from('Article a') ->leftJoin('a.User u') ->where('a.id = ?', 1) ->andWhere('a.publishable = ?', true); $article = $q->fetchOne();
SELECT
Doctrine_Query::select() permet d'indiquer les champs slectionner. Notez que pour l'instant, nous rcuprons tous nos enregistrements sous forme d'objets ; il est donc inutile de prciser quels champs rcuprer, car Doctrine slectionne automatiquement tous les champs. Nous verrons plus tard comment personnaliser tout a.
FROM
Doctrine_Query::from() permet d'indiquer la (ou les) table(s) sur lesquelles effectuer la slection. Pour ajouter d'autres champs une requte existante, utilisez Doctrine_Query::addFrom().
WHERE
Doctrine_Query::where() permet de filtrer les enregistrements. Utilisez la syntaxe classique de la clause WHERE du SQL. Notez que cette mthode efface toutes les conditions prcdemment dfinies. Pour ajouter une condition celles existantes, il faut utiliser Doctrine_Query::addWhere() (identique Doctrine_Query::andWhere()) qui ajoute une condition avec AND, ou Doctrine_Query::orWhere() qui ajoute une condition avec OR. Cette fonction accepte en deuxime argument la liste des paramtres : Code : PHP <?php $q->andWhere('a.id = $q->orWhere('a.title // Ou : $q->orWhere('a.title // Ou encore : $q->orWhere('a.title
? OR a.id = ?', array(1, 2)); = ?', array('Mon titre')); = ?', 'Mon titre'); = :title', array(':title' => 'Mon titre'));
JOIN
Doctrine supporte deux types de jointures :INNER JOIN et LEFT JOIN. RIGHT JOIN n'est pas support, mais il suffit de construire ses requtes autrement pour y pallier. Les mthodes utilises sont Doctrine_Query::innerJoin() et
www.siteduzero.com
20/67
Doctrine_Query::leftJoin(). Par dfaut, Doctrine ajoute automatiquement la condition de jointure (mot-cl ON) en fonction des relations dfinies entre les tables. Pour utiliser une condition personnalise, il suffit d'inclure le mot cl ON dans le DQL pass l'une de ces mthodes : Code : PHP <?php $q = Doctrine_Query::create() ->from('Article a') ->leftJoin('a.User u ON u.id = ?', 3);
Le mot-cl WITH (ajout d'une condition sur la jointure) s'utilise de la mme manire. Les paramtres sont passs de la mme faon que pour le WHERE (voir ci-dessus).
ORDER BY
La clause ORDER BY se dfinit avec Doctrine_Query::orderBy() (ou Doctrine_Query::addOrderBy()).
LIMIT et OFFSET
Utilisez Doctrine_Query::limit() et Doctrine_Query::offset(). Pour ceux qui formulent leurs limites en faisant LIMIT 14, 50 (avec MySQL par exemple), sachez que cela ne fait pas partie des spcifications officielles de SQL. L'autre formulation possible est LIMIT 50 OFFSET 14. Avec Doctrine, vous devrez donc utiliser la deuxime manire et utiliser limit() et offset().
GROUP BY
Utilisez Doctrine_Query::groupBy() ou Doctrine_Query::addGroupBy().
HAVING
Utilisez Doctrine_Query::having() ou Doctrine_Query::addHaving().
Il existe d'autres mthodes qui retournent des rsultats, telle que fetchArray(). Mais nous verrons cela plus tard (oui, encore ).
www.siteduzero.com
21/67
Pour finir, comme je vous l'ai dit, les mthodes qui excutent la requte (telles que execute() et fetchOne() ) acceptent elles aussi comme premier argument un tableau de paramtres passer. Pourquoi ? Et bien, il se peut qu'au moment de construire votre requte, vous ne connaissiez pas encore la valeur des variables que vous utiliserez l'intrieur. V ous avez donc la possibilit de les passer tout la fin, juste au moment de l'excution. Par exemple, les deux codes ci-dessous sont identiques : Code : PHP - Passage des paramtres lors de la construction <?php $q = Doctrine_Query::create() ->from('Article a') ->where('a.title = :title AND a.publishable = :publishable', array( ':title' => $title, ':publishable' => $publishable )) ->andWhere('a.content LIKE :keyword', '%'.$keyword.'%'); $resultats = $q->execute();
Code : PHP - Passage des paramtres lors de l'excution <?php $q = Doctrine_Query::create() ->from('Article a') ->where('a.title = :title OR a.publishable = :publishable') ->andWhere('a.content LIKE :keyword'); $resultats = $q->execute(array( ':title' => $title, ':publishable' => $publishable, ':keyword' => '%'.$keyword.'%', ));
Ceci peut vous tre utile lorsque vous utilisez des requtes comme modles. Notez que dans ce cas, il est conseill d'utiliser les paramtres nomms (tels que :title), moins que vous ne connaissiez par coeur l'ordre dans lequel vous devez passer les paramtres.
UPDATE et DELETE
Les mises jour et suppressions de donnes se font tout aussi simplement avec un objet Doctrine_Query.
DELETE
Ici, au lieu d'utiliser Doctrine_Query::select(), nous allons utiliser... Doctrine_Query::delete() ! Surprenant, hein ? Nous pouvons appliquer les mmes filtres que pour un SELECT ; voici un exemple : Code : PHP <?php $q = Doctrine_Query::create() ->delete('Article a') ->where('a.title LIKE ?', 'Mauvais titre') ->orWhere('a.published = ?', false)
www.siteduzero.com
22/67
UPDATE
De la mme manire, nous pouvons mettre jour des champs. Il faut en revanche spcifier en plus les champs mettre jour avec Doctrine_Query::set() : Code : PHP <?php $q = Doctrine_Query::create() ->update('Article a') ->where('a.title = ?', 'mon titre') ->orWhere('a.id = ?', 1) // Soit : ->set('content', 'un super texte'); // ou soit : ->set('content', '?', $content); // ou encore ->set(array('content' => $content)) $number = $q->execute();
Le nombre de lignes affectes (mises jour ou supprimes) est retourn par la mthode execute() dans le cas d'un update d'un delete.
Maintenant, lorsque nous utiliserons <?php Doctrine_Core::getTable('Article')->find(3), ce sera cette mthode qui sera appele (merci l'hritage ). Remarquez que nous avons utilis <?php $this->createQuery('a'). Ceci est en fait un raccourci pour :
www.siteduzero.com
23/67
Mais nous avons encore un problme : si on utilise une autre mthode que find() pour rcuprer un article, le filtrage ne sera pas effectu. Afin de simplifier l'ajout de cette condition toutes nos requtes, nous allons crer une mthode getPublishableArticleQuery() : Code : PHP <?php class ArticleTable extends Doctrine_Table { public function getPublishableArticleQuery(Doctrine_Query $q = null) { if(null === $q) { $q = $this->createQuery(); } // getRootAlias() renvoie l'alias principal de la requte // (jusqu' maintenant j'ai utilis 'a' pour cette table, // mais j'aurais pu utiliser n'importe quoi). $q->andWhere($q->getRootAlias().'.is_publishable = ?', true); } return $q;
public function find($id) { $q = $this->createQuery('a') ->where('a.id = ?', $id); return $this ->getPublishableArticleQuery($q) ->fetchOne();
Explications : La mthode ArticleTable::getPublishableArticleQuery() accepte en argument un objet Doctrine_Query, et lui ajoute une condition WHERE, puis la retourne. Nous voyons ici toute la puissance de Doctrine_Query : il est possible de modifier la requte plusieurs endroits dans notre programme, de lui ajouter des conditions, etc. sans se soucier de ce qu'elle fait. Notre mthode getPublishableArticleQuery ajoute la condition, sans savoir comment la requte est construite ni les lments qu'elle slectionne dj.
Il reste un petit inconvnient : dans toutes les requtes de slection telles que find() , nous devrons penser bien passer par cette mthode afin d'ajouter le filtre. Nous verrons plus tard une mthode beaucoup plus propre et pratique pour pallier cela, afin que ce soit fait automatiquement (oui oui, Doctrine rserve encore beaucoup de surprises... ). Je vous ai expliqu cela ici pour vous montrer la force de l'hritage : nous pouvons redfinir tous les comportements que nous voulons (tout en restant raisonnable, et en ne dtruisant pas tout... ). Pour conclure cette partie, notez que l'objet Doctrine_Connection ( ne pas confondre avec Doctrine_Co ll ection ) possde une mthode flush() , qui permet de sauvegarder tout ce qui a t modifi avec la connexion courante. C'est utile lorsque vous
www.siteduzero.com
24/67
modifiez un grand nombre d'objets et collections diffrents : cela vous vite de sauvegarder les objets/collections un par un.
www.siteduzero.com
25/67
Le contrleur
V oici la structure de notre contrleur (tout pas beau tout moche, je sais Code : PHP - ~/web/index.php <?php require(dirname(__FILE__).'/../config/global.php'); // On rcupre l'action effectuer (Create, Read, Update ou Delete). // Je compte sur vous pour utiliser un systme plus perfectionn que a. ^^ ):
www.siteduzero.com
26/67
Donnes de test
Avant de commencer, il va bien falloir avoir quelques donnes pour tester. Insrez donc ceci : Code : SQL - Donnes de base INSERT INTO member (login, password) VALUES ('Jean', 'mypass'), ('Bernard', 'mysuperpass'), ('Superman', 'batman'); INSERT INTO article (title, content) VALUES ("Mon premier article", "Voici le contenu de l'article, comme c'est simplement pour tester, je mets du texte tout bidon. D'ailleurs, je ne sais pas si vous avez remarqu, mais je n'ai pas beaucoup d'imagination !"), ("Mon second article", "Voici le contenu du deuxime article, comme c'est simplement pour tester, je mets du texte tout bidon. D'ailleurs, je ne sais pas si vous avez remarqu, mais je n'ai pas beaucoup d'imagination !"), ("Mon troisime et dernier article", "Voici le contenu du troisime article, comme c'est simplement pour tester, je mets du texte tout bidon. D'ailleurs, je ne sais pas si vous avez remarqu, mais je n'ai pas beaucoup d'imagination !");
Bien sr, si vous avez des donnes plus intressantes, utilisez-les. partir d'ici, je vais vous donner le code au fur et mesure que l'on avance. Je ne vous prends pas pour des gamins, si vous n'avez pas envie de rflchir et voulez copier/coller directement, libre vous. Mais dans ce cas, ce n'est presque pas la peine de lire jusqu'en bas, parce que je ne fais que reprendre ce que l'on a vu jusqu'ici. vous de voir ce que vous prfrez.
Affichage de donnes
Ici, nous voulons afficher une liste de tous nos articles dans un tableau. Nous allons devoir rcuprer les donnes dans la base de donnes, puis les afficher. Ce n'est vraiment pas compliqu, vous de jouer.
www.siteduzero.com
27/67
Cration de la requte
Ajoutez ceci dans le contrleur : Code : PHP - ~/web/index.php <?php /* ... */ case 'read': $articles = Doctrine_Core::getTable('Article')->findAll(); break; /* ... */
Comme nous l'avons vu, findAll() nous retourne tous les articles de la table.
Tout ceci est assez simple, je ne pense pas qu'il y ait de problme particulier. Nous utilisons simplement findAll() pour rcuprer tous les articles. Comme on rcupre un objet Doctrine_Collection (qui peut s'utiliser comme un tableau, rappelez-vous),
www.siteduzero.com
28/67
La notation tableau est recommande lorsque nous accdons l'objet dans du code HTML. Ceci, simplement parce que si la personne qui s'occupe d'crire le code HTML n'est pas la mme que celle qui crit le code PHP, elle n'est pas cense savoir comment PHP fonctionne. Cette notation lui permet de ne pas se soucier de la nature de la variable $article. Cela dit, si vous vous occupez de tout, vous faites bien comme vous voulez, vous avez quand mme tout fait le droit d'utiliser la notation objet si a vous chante.
Cration d'objets
Cette fois, il va nous falloir afficher un formulaire nous permettant de renseigner les informations sur un nouvel article. Attention, pour l'utilisateur, je suppose que vous ne voulez pas laisser le choix celui qui crit l'article de choisir qui crit l'article (logique, non ? ).
Le formulaire
Nous allons voir ici comment crer de nouveaux objets et les sauvegarder dans la base. Nous allons avoir besoin d'un formulaire afin de rentrer les informations : Code : PHP - ~/html/create.php <body> <h1>Cration d'un article</h1> <form action="index.php?action=create" method="post"> <label for="article_title">Titre : </label><br /> <input type="text" name="title" id="article_title" /><br /> <br /> <label for="article_text">Texte : </label><br /> <textarea name="content" id="article_text"></textarea><br /> <br /> <input type="submit" value="Sauvegarder" /> </form> <a href="?action=read">Retour la liste</a> </body>
Nous ne mettons pas de champ 'Auteur'. Personnalisez tout cela votre sauce, par exemple en rcuprant l'id de l'auteur depuis une variable de session.
www.siteduzero.com
29/67
Modification d'objets
La modification d'objets sera semblable la cration. La principale diffrence rside dans le fait que nous allons prremplir le formulaire avec les informations dj existantes. Il nous faut donc rcuprer l'article dans tous les cas. Et si on poste des informations le concernant, il faut le mettre jour et sauvegarder en consquence.
Le formulaire
Nous allons reprendre le formulaire de cration, en ajoutant simplement les valeurs par dfaut : Code : PHP - ~/html/update.php <body> <h1>Modification d'un article</h1> <form action="index.php?action=update" method="post"> <label for="article_title">Titre : </label><br /> <input type="text" name="title" id="article_title" value="<?php echo $article['title'] ?>" /><br /> <br /> <label for="article_text">Texte : </label><br /> <textarea name="content" id="article_text"><?php echo $article['content'] ?></textarea><br /> <br /> <input type="submit" value="Sauvegarder" /> <!-- Pour se souvenir de quel article il s'agit --> <input type="hidden" name="id" value="<?php echo $article['id'] ? >" /> </form> <a href="?action=read">Retour la liste</a> </body>
if(!empty($_POST)) { $article->title = $_POST['title']; $article->content = $_POST['content']; // On indique l'auteur. Adaptez cela votre projet, par exemple si vous stockez l'id dans la session. $article->User = $user; $article->save();
www.siteduzero.com
30/67
Petit exercice : trouvez-moi l'quivalent en construisant une requte (Doctrine_Query). Correction : Secret (cliquez pour afficher) Code : PHP <?php if(!empty($_POST)) { $q = Doctrine_Query::create() $q->update('Article a') ->set('a.title', '?', $_POST['title']) ->set('a.content', '?', $_POST['content']) ->where('a.id = ?', $id) ->execute(); }
Refactorisation du code
Ne voyez-vous pas quelque chose d'vident ? Les pages create et update sont pratiquement identiques, il est donc intressant de les fusionner en une seule.
Le formulaire
La page contenant le formulaire reste inchange. Si l'article est vide , alors les champs seront tout simplement prremplis avec des valeurs vides : Code : PHP - ~/html/update.php <body> <?php if($article->exists()): ?> <h1>Modification d'un article</h1> <?php else: ?> <h1>Cration d'un article</h1> <?php endif; ?> <form action="index.php?action=<?php echo $action ?>" method="post"> <label for="article_title">Titre : </label><br /> <input type="text" name="title" id="article_title" value="<?php echo $article['title'] ?>" /><br /> <br /> <label for="article_text">Texte : </label><br /> <textarea name="content" id="article_text"><?php echo $article['content'] ?></textarea><br /> <br /> <input type="submit" value="Sauvegarder" /> <?php if($article->exists()): ?> <input type="hidden" name="id" value="<?php echo $article['id'] ? >" /> <?php endif; ?> </form> <a href="?action=read">Retour la liste</a> </body>
www.siteduzero.com
31/67
Remarquez la mthode exists() de l'objet Doctrine_Record. Elle indique si l'objet existe dans la base de donnes, c'est-dire s'il est nouveau ou non.
Le contrleur
Le traitement devra crer l'objet au cas o il n'existe pas : Code : PHP - ~/web/index.php <?php /* ... */ case 'create': case 'update': if(!isset($_GET['id']) && !isset($_POST['id'])) { $article = new Article(); } else { $id = isset($_POST['id']) ? $_POST['id'] : $_GET['id']; if(!($article = Doctrine_Core::getTable('Article')->find($id))) { $article = new Article(); } } if(!empty($_POST)) { $article->title = $_POST['title']; $article->content = $_POST['content']; // On indique l'auteur. Adaptez cela votre projet, par exemple si vous stockez l'id dans la session. $article->User = $user; $article->save(); } break; /* ... */
Dans le cas o la page est appele avec un id, on l'utilise pour rcuprer l'article existant correspondant. Sinon, on en cre un nouveau. Ensuite, peu importe que l'article soit un nouveau ou un existant, le traitement est le mme pour modifier les valeurs et sauvegarder. Par ailleurs, le fichier create.php ne nous sert maintenant plus rien.
Suppression de donnes
La suppression de donnes est trs simple. Pour la vue, je vous invite juste afficher un message de confirmation, par exemple, dans le fichier ~/html/delete.php, vous devriez y arriver sans problme.
Traitement
Code : PHP - ~/web/index.php <?php /* ... */
www.siteduzero.com
32/67
Trouvez-moi maintenant l'quivalent avec une Doctrine_Query Secret (cliquez pour afficher) Tout simplement : Code : PHP <?php $q = Doctrine_Query::create() $q->delete('Article a') ->where('a.id = ?', $id) ->execute(); Ce TP (extrmement simple, non ? ) vous a permis de mettre en pratique simplement ce que nous avons vu jusque l. Bien sr, comme je l'ai dj dit, l'intrt de Doctrine se rvle surtout dans les gros projets, et je ne vous encourage pas forcment l'utiliser pour de petits projets simples. Attention, comme je l'ai dj dit dans l'introduction, ce TP n'est pas destin tre un systme complet. Je n'ai notamment pas mis l'accent sur la scurit, mais il faut bien entendu vous assurer dans chaque action que les donnes correctes sont fournies.
Sur ce, et si vous voulez en savoir plus sur Doctrine, passons maintenant des chapitres un peu plus complexes. Le suivant concerne les templates (ou behaviors). V oil, vous devez tre maintenant capables d'utiliser Doctrine dans vos projets. V ous verrez, avec un peu de pratique, a vous simplifiera grandement la tche. Si vous tes un peu curieux, je vous invite maintenant entrer un peu plus en profondeur dans les entrailles de Doctrine (oul, a fait peur ! ). Plus vous en connaitrez sur cet ORM, plus vous l'utiliserez d'une faon optimale. Nous verrons beaucoup de trucs utiles et pratiques, qui simplifieront encore votre code. Je vous encourage vivement ne pas vous arrter ici.
www.siteduzero.com
33/67
Les diffrents composants de Doctrine dclenchent plusieurs moments ces vnements. Nous allons les tudier juste aprs.
Intercepter un vnement
C'est le rle des listeners et des hooks. Un hook est une mthode vide intgre l'objet cout. Un listener est un objet part, qui est li l'objet cout. Il implmente lui-mme les hooks, ce qui permet une meilleure sparation et organisation des diffrentes facettes de l'application.
V ous comprendrez mieux tout cela par la suite, ne vous inquitez pas. Les classes susceptibles de dclencher des vnements, sont les suivantes : Doctrine_Record ; Doctrine_Validator ; Doctrine_Connection ; Doctrine_Transaction ; Doctrine_Connection_Statement.
www.siteduzero.com
34/67
Dans cet exemple, nous utilisons la mthode preSave() (un hook). Comme son nom l'indique, cette mthode est appele juste avant l'enregistrement de l'article dans la base de donnes. Nous en profitons pour indiquer que c'est l'utilisateur courant qui a modifi cet article en dernier. Comme nous sommes l'intrieur de l'objet en question, nous avons bien entendu accs la variable $this, et nous pouvons le modifier directement, juste avant son enregistrement. Les mthodes utilises respectent toutes la mme logique : elles sont prfixes par pre ou post , selon qu'elles soient appeles avant ou aprs l'vnement (ici, save). Pour finir, elles reoivent en paramtre un objet $event, instance de Doctrine_Event . Mais comment Doctrine peut-il savoir que j'ai crit la mthode preSave() si je ne le lui dis pas ?
En fait, tous les hooks sont dj dfinis dans la classe Doctrine_Record : allez y jeter un coup d'il. Ce sont des mthodes vides, et Doctrine les appelle chaque vnement. Seulement, tant que vous ne redfinissez pas cette mthode dans une classe fille (merci l'hritage ), et bien il ne se passe rien ! C'est la mme chose chaque fois : l'astuce est simplement d'utiliser des mthodes vides !
Doctrine_Event
Les objets Doctrine_Event permettent d'agir sur certains objets. Par exemple, si l'vnement est l'excution d'une requte, on peut rcuprer celle-ci, et ainsi la modifier. V oici quelques mthodes utilises frquemment :
getInvoker()
Retourne l'objet qui a invoqu l'vnement. Par exemple, un objet Doctrine_Record . Il y a toujours un Invoker, et il s'agit d'une instance de l'une des classes cites plus haut. Nous pouvons donc reformuler l'exemple prcdent : Code : PHP <?php class Article extends Doctrine_Record { public function preSave($event) { $event->getInvoker()->user_id = $_SESSION['user_id']; } }
getQuery()
www.siteduzero.com
35/67
getParams()
Retourne un tableau d'ventuels paramtres. Par exemple, le paramtre alias contient l'alias utilis dans la requte. V oici les mthodes les plus courantes. Je vous invite ouvrir le fichier Event.php pour plus de dtails. Si vous avez bien suivi le dbut du tuto, vous devriez le trouver dans ~/lib/vendor/doctrine/Doctrine/.
Listener de connexion
Dans ce chapitre, nous nous intressons aux listeners de connexion, c'est--dire les listeners que l'on peut attacher la classe Doctrine_Connection . Je vous l'ai dit dans le chapitre prcdent, un listener est une classe qui implmente des mthodes spcifiques.
Crer un listener
Il existe trois manires de crer un listener.
Ouvrez le fichier ~/lib/vendor/doctrine/Doctrine/EventListener.php. V ous devriez maintenant mieux comprendre l'astuce des mthodes vides que je vous avais explique. La seule chose qu'il vous reste faire maintenant, c'est de surcharger les mthodes que vous voulez implmenter. Chaque mthode porte un nom assez explicite, et je pense que vous devriez vous y retrouver.
www.siteduzero.com
36/67
Bien sr, c'est ici que vous personnalisez chaque mthode comme vous le voulez.
Le faux listener...
La dernire manire de crer un listener, est d'implmenter l'interface Doctrine_Overloadable. Cette interface n'impose qu'une chose : l'implmentation de la mthode magique __call(). Cette mthode sera appele lors de chaque vnement, vous ensuite de traiter tout a comme vous le voulez. Pour rappel, la mthode __call() reoit deux paramtres : le nom de la mthode appele, et un tableau des arguments.
Code : PHP <?php class CustomListener3 implements Doctrine_Overloadable { public function __call($method, $arguments) { $event = $arguments[0]; } }
Attacher un listener
La dernire chose savoir, c'est comment lier le listener l'objet cout. Ceci se fait d'une manire trs simple : Doctrine_Connection possde la mthode addListener() qui prend en paramtre... un listener ! Un exemple est plus simple que de longues explications : Code : PHP <?php $listener1 = new CustomListener(); $listener2 = new CustomListener2(); $connexion->addListener($listener1); $connexion->addListener($listener2);
Comme vous le voyez, il est possible d'ajouter plusieurs listeners. Lors d'un vnement, ils seront appels dans le mme ordre que celui dans lequel ils ont t affects. Notez que s'il n'y a qu'un seul listener, vous pouvez utiliser la mthode setListener() . Cela supprime galement d'ventuels listeners existants.
www.siteduzero.com
37/67
Je vous invite vous rfrer la documentation officielle (in english of course ) pour avoir le dtail de chaque mthode. Dans les tableaux, la colonne de gauche indique le nom de la mthode du listener, celle du milieu indique quelle mthode est coute, et la troisime contient d'ventuels paramtres accessibles de l'objet Doctrine_Event . Par exemple, lors de l'appel de Doctrine_Connection::connect(), avant toute action, la mthode preConnect() du/des listener(s) est appele. Une fois que toutes les actions sont effectues, c'est au tour de la mthode postConnect() . Je vous rappelle aussi que toutes les mthodes reoivent un objet Doctrine_Event en paramtre. Je ne m'attarde pas plus sur les listeners de connexion, a ne vous sera pas forcment utile pour une utilisation basique.
Listeners d'objets
Nous allons voir ici des listeners que vous trouverez certainement plus utiles. Ceux-ci sont appels par les classes Doctrine_Record et Doctrine_Validator.
Crer un listener
De la mme manire que pour les connections, il y a trois faons de crer un listener : hriter de la classe Doctrine_Record_Listener ; implmenter l'interface Doctrine_Record_Listener_Interface ; implmenter l'interface Doctrine_Overloadable.
La manire de crer cette classe est identique Doctrine_Connection , rfrez-vous donc au chapitre prcdent. V oici le lien vers la doc pour plus de dtails et la liste des mthodes : www.doctrine-project.org.
Attacher un listener
Notre listener peut tre attach au niveau global, au niveau d'une connexion ou au niveau d'une table.
Globalement
Il faut l'attacher Doctrine_Manager. Toutes les tables de toutes les connexions en bnficieront alors. Code : PHP <?php class CustomRecordListener extends Doctrine_Record_Listener { /* ... */ } Doctrine_Manager::getInstance()->addRecordListener(new CustomRecordListener());
Par connexion
En attachant le listener une instance de Doctrine_Connexion , toutes les tables de cette connexion sont affectes. Nanmoins, je pense que la plupart du temps, vous n'avez qu'une connexion une seule base de donnes dans votre projet. Dans ce cas, cela quivaut l'attacher globalement.
www.siteduzero.com
38/67
Par table
C'est certainement la fonctionnalit la plus intressante, ou du moins celle dont vous vous servirez le plus. Il est possible d'attacher un listener une table particulire. Code : PHP <?php Doctrine_Core::getTable('Article') ->addRecordListener(new CustomRecordListener()); // quivalent : class Article extends Doctrine_Record { public function setUp() { $this->addListener(new CustomRecordListener()); } }
Les hooks
Je vous en avais parl en introduction, les hooks sont des listeners simplifis . Ce sont de simples mthodes implmentes directement dans l'objet. Code : PHP <?php class Article extends Doctrine_Record { public function preSave($event) { // On fait ce que l'on veut juste avant l'enregistrement de l'objet. } }
Comme vous le voyez, les hooks sont plus simples utiliser que les listeners. Cependant, lorsque vous devez en utiliser beaucoup, prfrez les listeners, cela permet une meilleure organisation du code.
www.siteduzero.com
39/67
Leur fonctionnement est identique aux autres mthodes vues prcdemment. Notez cependant que Doctrine_Event::getQuery() retourne dans ce cas la requte elle-mme, ce qui offre la possibilit de la modifier. Code : PHP <?php class CustomRecordListener extends Doctrine_Record_Listener { public function preDqlSelect(Doctrine_Event $event) { $event->getQuery()->addWhere('ce que vous voulez...'); } }
V ous pouvez ainsi filtrer des donnes lors d'un SELECT, par exemple. Attention ! Par dfaut, ces trois mthodes ne sont pas appeles ! Pour les utiliser, il faut le spcifier explicitement, comme ceci : <?php $manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true). En effet, ceci ajoute une surcharge de travail, puisque Doctrine doit, pour chaque requte, appeler ces callbacks pour chaque table incluse dans la clause FROM. Rendez-vous dans la partie Annexes pour plus de dtails sur les attributs de Doctrine_Manager.
Attention, avec cet exemple, vos objets ne pourront pas tre enregistrs avec la mthode save() ...
www.siteduzero.com
40/67
class CustomRecordListener2 extends Doctrine_Record_Listener { public function preSave(Doctrine_Event $event) { /* ... */ } } $record->addListener(new CustomRecordListener1()); $record->addListener(new CustomRecordListener2()); $record->save();
En temps normal, les mthodes preSave() de chaque listener sont appeles dans l'ordre (CustomRecordListener1::preSave() puis CustomRecordListener2::preSave()). Cependant ici, CustomRecordListener1::preSave() demande de sauter le prochain listener. CustomRecordListener2::preSave() ne sera donc jamais excute. Ce petit chapitre est maintenant termin et il vous a permis de dcouvrir les Events Listeners de Doctrine. Bien que ce chapitre soit trs thorique, dans le prochain nous tudierons les templates. Ce qui permettra de mettre en pratique avec un TP tout ce que vous avez appris : vous ajouterez un listener un template...
www.siteduzero.com
41/67
Les templates
Nous allons maintenant nous attaquer une partie importante de Doctrine : les templates (on parle aussi de behavior). Ces behaviors vont nous tre utiles lorsque nous avons plusieurs modles qui partagent des points communs. Ces points communs peuvent tre : des colonnes de la base de donnes ; des algorithmes ; des relations ; n'importe quoi d'autre...
Dans ce tutoriel, j'utiliserai indiffremment les termes template ou behavior. Littralement, template signifie modle alors que behavior se traduirait par comportement .
Exemples
Peut-tre utilisez-vous un systme de templates dans votre application afin de sparer le code PHP de la mise en page (en HTML ou autre). Ceci offre un avantage considrable : l'utilisation, par exemple, d'une page HTML sans se soucier de comment sont rcupres les donnes. On se contente de les afficher. Cette page HTML (template) peut alors tre rutilise par plusieurs scripts diffrents, tout comme le script peut envoyer ses informations plusieurs pages HTML diffrentes. Un autre exemple, qui n'existe pas en PHP d son typage dynamique faible, mais dans d'autres langages comme le C++, est le template de fonctions. Cela permet d'outrepasser le fait qu'une fonction ne peut accepter qu'un type de paramtre dfini l'avance. Mais je ne m'tends pas trop l-dessus, puisque nous avons affaire au PHP.
Imaginez que les dveloppeurs veuillent changer quelque chose dans la gestion de la datation des objets. V ous croyez qu'ils vont s'embter modifier toutes les classes une par une ? Les pauvres ! Bref, je vous ai assez fait mariner comme a. Avec Doctrine, nous allons pouvoir dfinir des modles de classes. Lorsque vous aurez plusieurs objets (articles, etc.) dater, vous aurez juste dire je veux le dater ! et Doctrine s'en occupera automatiquement pour vous. Elle est pas belle la vie ?
www.siteduzero.com
42/67
Je ne comprends pas. D'habitude, lorsque j'ai des points communs entre des classes, j'utilise l'hritage, ce n'est pas comme a qu'il faut faire ? Et bien, a dpend. (Quoi, vous vous attendiez une autre rponse ?
)
Lorsque des classes ont entre elles une relle relation parent-enfant, alors oui, l'hritage est l pour a et il faut l'utiliser. Mais lorsque ce n'est pas le cas, mais qu'il y a des points communs, il parat stupide de dupliquer du code. De plus, PHP lui-mme, en ne supportant pas l'hritage multiple, nous limite. La solution pour pallier cela : les templates !
Schma en Yaml
Il suffit d'une ligne dans le schma Yaml pour appliquer un behavior : Code : Autre - ~/config/schema.yml Article: actAs: Timestampable: ~ columns: title: type: string(255) notnull: true # ...
J'ai pris comme exemple le template nomm Timestampable (nous l'tudierons juste aprs). Comme vous le voyez, nous ajoutons une cl actAs qui indique la liste des behaviors et de leurs options. Ici, il n'y a pas d'option particulire (d'o le ~, qui est quivalent null , pour rappel). Notez que si aucun de vos templates n'a d'option, vous pouvez utiliser la notation simple de tableau : Code : Autre Article: actAs: [Timestampable, UnAutreTemplate] # ...
www.siteduzero.com
43/67
Schma en PHP
En PHP, il est recommand de placer la dclaration dans la mthode setUp() : Code : PHP - ~/lib/models/generated/BaseArticle.php <?php class BaseArticle extends Doctrine_Record { /* ... */ public function setUp() { /* ... */ } $this->actAs('Timestampable');
Pour ceux qui gnrent les classes de base partir du Yaml, vous aurez peut-tre remarqu que le code gnr n'est pas tout fait identique : Code : PHP <?php $timestampable0 = new Doctrine_Template_Timestampable(); $this->actAs($timestampable0);
C'est un objet qui est pass la mthode actAs() . En effet, les templates sont des classes nommes de la forme Doctrine_Template_ NomDuTemplate. Si vous passez juste son nom, Doctrine essayera de l'instancier lui-mme. Nous allons maintenant passer en revue les templates intgrs.
Timestampable
C'est probablement le template le plus connu. Il donne le moyen de dater facilement les objets (date de cration et date de mise jour). Tous vos objets dclars comme timestampables se voient ajouter deux nouvelles colonnes : created_at et updated_at . Notez qu'il est possible de personnaliser leur nom, ou mme de n'utiliser que l'une des deux. Jetez un il : Code : Autre # ... actAs: Timestampable: created: disabled: true updated: name: updated_date onInsert: false format: Y-m-d # ...
www.siteduzero.com
44/67
Les options sont passes en deuxime argument de actAs() , qui lui-mme les passe au constructeur du template. Pour voir toutes les options disponibles, je vous invite ouvrir le fichier ~/lib/vendor/doctrine/Doctrine/Template/Timestampable.php. Les options par dfaut sont dfinies dans l'attribut $_options. V oyez ci-dessous pour quelques explications sur chacune. (Notez que cela n'a pas beaucoup de sens de dsactiver le champ created_at et de mettre l'option onInsert de updated false, c'est juste un exemple. ) Un template possde un comportement trs proche d'un objet. Il a, entre autres, la possibilit d'avoir des listeners. Je ne vous en parle pas plus pour l'instant, vous comprendrez leur fonctionnement lorsque nous en crerons un par la suite. Sachez simplement que cela donne la possibilit d'intervenir juste avant certains vnements, par exemple juste avant la sauvegarde d'un objet dans la base de donnes. Et c'est prcisment comme a que le template Timestampable fonctionne : juste avant que vous sauvegardiez votre objet (mthode save() par exemple), Doctrine va mettre jour ces champs avec la valeur de timestamp courante. V ous n'avez donc jamais vous soucier de ces champs. La seule chose qui compte pour vous, c'est de pouvoir les rcuprer. Et cela se fait tout naturellement en y accdant comme on le ferait pour n'importe quel autre champ : Code : PHP <?php echo $article->created_at;
Rien qu'avec ce premier exemple, assez banal en fait, vous voyez la subtilit et la puissance des behaviors. Doctrine fournit un comportement par dfaut qui correspond la plupart des besoins, mais en mme temps vous laisse la possibilit de personnaliser chaque dtail.
Options disponibles
Les options suivantes sont personnalisables. Elles sont identiques pour chacun des deux champs (created et updated ). name : le nom de la colonne (par dfaut, created_at et updated_at ). alias : un alias pour chaque colonne. Par dfaut il n'y en a pas. type : type du champ, par dfaut timestamp . format : le format (ncessaire pour un champ de type timestamp/date). Par dfaut Y-m-d H:i:s . disabled : si gal true, dsactive cette colonne. Vaut false par dfaut. expression : donne la possibilit d'utiliser une Doctrine_Expression pour le champ. Dsactiv par dfaut. options : options passer pour la dfinition de la colonne. Par dfaut, indique seulement que la colonne ne peut tre vide (NOTNULL). onInsert : utilis uniquement sur le champ updated . Indique s'il doit tre mis jour aussi lors de la cration de l'objet. Par dfaut, vaut true.
www.siteduzero.com
45/67
SoftDelete
Ne vous tes-vous jamais dit Ah mince ! Je voulais le garder ! aprs avoir supprim un article (ou n'importe quoi d'autre) ? SoftDelete est l pour vous ! Je ne vais pas remettre chaque fois le code de l'implmentation Yaml ou PHP, a fonctionne chaque fois de la mme manire. Seules les options sont variables.
Ce template vous permet de ne pas supprimer rellement vos objets. Il ajoute une colonne deleted_at dans votre modle, qui indique la date laquelle l'objet a t supprim. chaque fois que vous ferez <?php $objet->delete(), votre objet ne sera pas supprim. la place, sa colonne deleted_at sera remplie avec la date courante. Oui mais c'est pas trs pratique tout a, maintenant chaque fois que je rcuprerai un objet depuis ma base de donnes, il faudra que je vrifie s'il a t supprim ou non. C'est nul ton truc ! V ous croyez donc que notre ORM prfr ne gre pas a ? Erreur ! En effet, ce template ajoute un filtrage automatique de tout ce que vous slectionnez dans la base. Et il ne vous renverra pas les lments virtuellement supprims. De votre ct, vous ne vous en souciez donc vraiment plus. Pour cela, il faut changer la valeur d'un attribut de Doctrine_Manager : Code : PHP <?php Doctrine_Manager::getInstance() ->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true);
En effet, ce template utilise les callbacks DQL dans un listener. Je ne dtaille pas plus pour l'instant, mais si vous avez lu le chapitre prcdent, vous devriez comprendre de quoi il s'agit. Placez ceci dans ~/config/global.php par exemple. Notez que vous pouvez quand mme rcuprer ces enregistrements supprims : il faut pour cela le spcifier explicitement dans une requte : Code : PHP <?php $deletedArticles = Doctrine_Query::create() ->from('Article a') ->where('a.deleted_at IS NOT NULL') // Si deleted_at contient une valeur (une date), c'est que l'article est supprim. ->execute();
V ous avez aussi la possibilit de supprimer dfinitivement vos objets : Code : PHP <?php foreach($deletedArticles as $article) { $article->hardDelete(); }
www.siteduzero.com
46/67
Comme vous le voyez, le template ajoute aussi une mthode hardDelete() aux objets (oui, un template, a peut faire a aussi ). Maintenant, si vous supprimez des enregistrements, c'est parce que vous l'aurez bien voulu !
Options disponibles
name : nom de la colonne. Par dfaut, c'est deleted_at . type : type de la colonne, timestamp par dfaut. V ous pouvez aussi utiliser boolean pour simplement indiquer si l'article est supprim ou non, sans la date. hardDelete : indique si les enregistrements doivent tre supprims dfinitivement. Par dfaut, vaut false. Je ne vous conseille pas de le mettre true, sinon le template n'est pas extrmement utile... options : options passer lors de la construction de la colonne.
Versionable
Ce template vous donne la possibilit de versionner vos objets. C'est--dire que vous conservez tout l'historique des modifications apportes chaque objet, et que vous avez tout moment la possibilit de revenir en arrire et d'annuler des modifications. En fait, Doctrine va crer une deuxime table (dont vous ne vous servirez jamais directement), qui va stocker toutes les anciennes versions. Cette table est nomme avec le suffixe '_version'. Une colonne version est aussi ajoute, qui stocke le numro de version de l'objet. Du point de vue utilisateur (c'est--dire vous ), vous ne vous apercevez de presque rien : vous voyez le numro de version de votre objet s'incrmenter chaque fois que vous y apportez des modifications et le sauvegardez. En interne, chaque modification/suppression, l'ancienne version est dplace dans la table ddie, et la dernire version est enregistre normalement dans la table avec un numro de version incrment. Pour finir, vous avez la possibilit de restaurer une ancienne version, grce la mthode revert() : Code : PHP <?php // Nous restaurons l'objet tel qu'il tait dans sa version N2. $objet->revert(2);
Attention cependant ne pas abuser de ce behavior. Le fait d'ajouter une table supplmentaire pour chaque table versionnable, et de sauvegarder toutes les versions, peut finir par faire augmenter considrablement la taille de votre base de donnes.
Options disponibles
Je vous invite, comme toujours, aller voir les options disponibles dans ~/lib/vendor/doctrine/Doctrine/Template/Versionable.php. V oici quelques explications : generateFiles : Doctrine gnre une classe supplmentaire pour la table de version. Par dfaut, ces classes ne sont pas cres en dur , mais gnres la vole. V ous pouvez forcer crer ces fichiers en passant cette option true. deleteVersions : Indique tout simplement si oui ou non les anciennes versions doivent tre supprimes lorsque la
www.siteduzero.com
47/67
Sluggable
Si vous avez dj eu envie de faire apparatre le titre de vos articles (par exemple) dans leur URL, vous vous tes srement heurt au fait que certains caractres posent problme. Doctrine apporte une solution cela aussi, avec le template Sluggable. Celui-ci va ajouter une colonne slug qui contiendra le titre de vos articles, mais adapt une URL (suppression des caractres spciaux, etc.). Pour indiquer sur quels champs doit tre bas le slug , il faut renseigner l'option fields. Dans le cas de notre article, nous indiquerons par exemple la colonne title. V ous avez aussi la possibilit de forcer cette colonne tre unique. De cette manire, vous pourrez retrouver un objet prcis sans utiliser son ID, mais uniquement avec son slug . Pour cela, passez l'option unique true. Si plusieurs objets ont le mme slug, alors un chiffre sera ajout pour les diffrencier. V ous avez la possibilit de dterminer un slug unique avec des champs particuliers : indiquez-les avec uniqueBy.
Options disponibles
V oici un rsum des options : unique : si oui ou non l'unicit du champ doit tre garantie. True par dfaut. fields : champs utiliser pour le slug . uniqueBy : champs par lesquels l'unicit est garantie. uniqueIndex : si oui ou non l'index est cr sur la colonne. indexName : nom donner l'index. canUpdate : si le slug peut tre mis jour chaque modification de l'objet, ou s'il est dfini une fois pour toutes. builder : fonction utiliser pour construire le slug. provider : fonction utiliser pour rcuprer une valeur reprsentative de l'objet pour construire le slug. Est utilis uniquement si aucun champ n'est indiqu dans l'option fields .
Comment a marche ?
Si vous avez lu le dbut du paragraphe sur l'utilisation des templates, vous devez vous souvenir qu'un template est une classe (par exemple, Doctrine_Template_Timestampable). Ouvrez donc le fichier ~/lib/vendor/doctrine/Doctrine/Template/Timestampable.php si vous ne l'avez pas dj fait, vous suivrez mieux ce qui va suivre. Nous allons tudier cette classe en dtail, elle nous servira d'exemple.
Doctrine_Template
Premire chose que vous remarquez, elle hrite d'une classe de base : Doctrine_Template. Cette classe fournit les lments essentiels au template : Des mutateurs pour accder l'invoker, c'est--dire le dernier enregistrement avoir utilis ce template : setInvoker($invoker) et getInvoker(). La possibilit d'utiliser un plug-in. Un plug-in est utile lorsque le template implique de faire appel un fonctionnement relativement complexe. Par exemple, le template Doctrine_Template_Searchable (je ne vous en ai pas encore parl) fait appel Doctrine_Search . Le fonctionnement de ce plug-in est indpendant du template, et peut tre utilis de l'extrieur. Je vous laisse trouver les mthodes correspondantes... (Doctrine_Template::getPlugin() etc.) Un accesseur vers la table de l'invoker : getTable() . On rcupre simplement une instance de Doctrine_Table.
www.siteduzero.com
48/67
Hritage
Doctrine_Template possde aussi deux mthodes vides : setUp() et setTableDefinition() . Comme pour le modle, ces mthodes servent dfinir, entre autres, la table et les relations. Les mthodes hasColumn() , hasOne() , hasMany() ... sont utiliser ici. Et l vous allez me dire... Eh ! Mais a ressemble pas un peu Doctrine_Record a ?
Si !
En effet, il faut bien comprendre qu'un objet est fusionn avec les templates qui lui sont associs (c'est une image hein Cela lui permet de partager tout ce que l'on vient de voir, comme des relations, etc. ainsi que des mthodes !
).
Mthodes dlgues
Un template offre la possibilit d'ajouter des mthodes aux objets et aux tables.
www.siteduzero.com
49/67
Nous imaginons ici une mthode isOlderThan() qui indique si la date de cration de l'objet est plus ancienne que la date passe en paramtre. Maintenant, nous pouvons utiliser cette mthode comme ceci : Code : PHP <?php if($article->isOlderThan("yesterday")) { echo "Cet article a t publi avant hier."; } else { echo "Cet article a t publi hier ou aujourd'hui."; }
V ous l'aurez devin, lorsque vous voulez rendre accessible une mthode depuis la classe de table, il faut suffixer son nom de TableProxy. Et vous pouvez l'utiliser comme ceci : Code : PHP <?php $table = Doctrine_Core::getTable('Article'); // Ne pas mettre 'TableProxy' lors de l'utilisation de la mthode. $articles = $table->findArticlesOlderThan("yesterday");
www.siteduzero.com
50/67
www.siteduzero.com
51/67
Introduction
Nous allons dtailler ici toutes les tapes de la cration de notre template. Je vais continuer avec l'exemple de notre article, que j'utilise depuis le dbut du tutoriel. Nous voulons maintenant que le nom de l'auteur original (celui qui a cr l'article), et le nom de la dernire personne l'avoir modifi, soient enregistrs automatiquement. Nous allons aussi ajouter un champ qui indiquera si l'article est disponible ou non en lecture pour les visiteurs. Le fonctionnement ressemblera SoftDelete et cela permettra d'assimiler un peu mieux le fonctionnement des vnements. Je vous propose de nommer notre template Publishable. Avant de commencer, vous pouvez supprimer la colonne user_id et la relation Article-User que nous avons dfinies dans le schma. N'oubliez pas ensuite de tout reconstruire en appelant ~/web/builder.php.
Cration de la classe
Je vous ai dit dans le chapitre prcdent que le fonctionnement des templates ressemblait celui des objets Doctrine_Record . Et pour cause : Doctrine_Template hrite de la mme classe de base, Doctrine_Record_Abstract . Ils utilisent donc les mmes mthodes pour se configurer : setUp() et setTableDefinition() . Nous dfinirons donc les colonnes ajouter l'intrieur de setTableDefinition() , tandis que setUp() servira dfinir les relations. Nous allons placer tous nos templates dans ~/lib/models/templates/. Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { }
Notez que j'ai prfix le nom du template de Doctrine_Template_ , mais vous n'y tes pas obligs. Si vous utilisez un prfixe particulier dans votre projet, vous pouvez tout fait l'utiliser, ou mme l'appeler Publishable tout court. Seul petit dtail : si vous placez un prfixe (autre que Doctrine_Template_ ), il vous faudra utiliser le nom complet lors de la dfinition avec actAs() , sinon Doctrine ne pourra pas le trouver et le charger automatiquement. moins de dfinir votre propre autoloader... mais a n'entre pas dans le cadre de ce cours. Bref, vous faites comme vous voulez.
Dtails supplmentaires
V ous avez le code de base de la classe, maintenant, vous de jouer ! Trois colonnes seront ncessaires pour ce template. Nommez-les comme vous le voulez. Deux d'entre elles contiendront des rfrences des utilisateurs (par exemple leur ID). La troisime sera simplement de type boolen. Notez qu'il n'est pas obligatoire du tout d'utiliser l'attribut $_options. Nanmoins, je vous encourage le faire, la personnalisation depuis le schma tant impossible sinon. vous de choisir quelles options implmenter (aidez-vous des
www.siteduzero.com
52/67
Correction : le template
V ous avez bien travaill ? V ous n'avez pas trouv comment faire ? V oici ci-dessous la correction dtaille.
Les options
Tout d'abord, voici un exemple d'options possibles. Bien sr, ce n'est pas LA correction unique, vous mettez les options que vous voulez ! Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { protected $_options = array( 'created' => array( // Nom de la colonne 'auteur original'. 'name' => 'created_by', // Alias. On pourra faire $article->OriginalAuthor. 'alias' => 'OriginalAuthor', // Classe reprsentant un auteur. 'class' => 'User', // Champ de la classe User servant la relation. 'foreign' => 'id', // Type du champ, on stocke l'ID, donc integer. 'type' => 'integer', // On donne la possibilit de ne pas utiliser cette colonne. 'disabled' => false, // Options supplmentaires pour la colonne (notnull, default, etc.). 'options' => array(), ), // Mmes options pour la colonne du dernier auteur. 'updated' => array( 'name' => 'updated_by', 'alias' => 'Author', 'class' => 'User', 'foreign' => 'id', 'type' => 'integer', 'disabled' => false, 'options' => array(), ), // Options pour la colonne 'publiable'. 'publishable' => array( 'name' => 'is_publishable', 'alias' => null, 'type' => 'boolean', 'disabled' => false,
www.siteduzero.com
53/67
true), ); }
/* ... */
Comme vous le voyez, j'ai repris les options prsentes dans la plupart des templates : on donne la possibilit de dsactiver certaines colonnes, d'ajouter des alias pour accder aux auteurs, etc. L'option class indique quelle classe sera utilise pour la relation (c'est--dire celle qui reprsente un auteur, un membre, etc. : toute personne susceptible de rdiger l'article), et foreign indique le nom de la colonne sur laquelle la relation est dfinie (par dfaut, id ).
Les colonnes
Nous allons, comme nous l'avons dit, ajouter deux champs notre table. Il n'y a rien d'extraordinaire, c'est exactement pareil que lors de la dfinition du schma dans les classes de base. Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { /* ... */ public function setTableDefinition() { if(!$this->_options['created']['disabled']) { $this->hasColumn( $this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options'] ); } if(!$this->_options['updated']['disabled']) { $this->hasColumn( $this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options'] ); } if(!$this->_options['publishable']['disabled']) { $name = $this->_options['publishable']['name']; if($this->_options['publishable']['alias']) { $name .= ' as '.$this>_options['publishable']['alias']; } $this->hasColumn( $name, $this->_options['publishable']['type'], null, $this->_options['publishable']['options'] );
www.siteduzero.com
54/67
Nous vrifions pour chaque lment qu'il n'est pas dsactiv, et nous ajoutons la colonne au modle en consquence, en utilisant les options dfinies plus haut. Encore une fois, rien de spcial, la dfinition des colonnes se fait de la mme manire que dans les classes de base.
Les relations
Nous avons choisi d'enregistrer l'ID de l'auteur original, et l'ID du dernier auteur avoir modifi l'article. Nous allons donc dfinir deux relations dans la mthode setUp() . Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { /* ... */ public function setUp() { if(!$this->_options['created']['disabled']) { $name = $this->_options['created']['class']; if($this->_options['created']['alias']) { $name .= ' as '.$this->_options['created']['alias']; } $this->hasOne($name, array( 'local' => $this->_options['created']['name'], 'foreign' => $this->_options['created']['foreign'] ));
if(!$this->_options['updated']['disabled']) { $name = $this->_options['updated']['class']; if($this->_options['updated']['alias']) { $name .= ' as '.$this->_options['updated']['alias']; } $this->hasOne($name, array( 'local' => $this->_options['updated']['name'], 'foreign' => $this->_options['updated']['foreign'] ));
Une fois encore, la manire d'indiquer les relations ne diffre pas de lors de la dfinition du schma. L'option name est utilise en tant que cl locale, et foreign en tant que cl trangre. class indique la classe lier, et alias un ventuel alias (par dfaut, OriginalAuthor et Author). V oil, ce stade, nous avons dfini toutes les colonnes et relations ncessaires notre template. Nous pouvons par exemple accder au login de la dernire personne ayant modifi un article : Code : PHP <?php echo $article->Author->login;
www.siteduzero.com
55/67
Correction : le listener
Sans le listener associ, notre template serait bien peu pratique.
Rflchissons ce que nous voulons obtenir. Nous voulons que l'auteur soit ajout lors du premier enregistrement de notre article. La mthode utiliser est preInsert(). Nous voulons aussi qu' chaque modification, le nom de l'auteur soit galement mis jour. Nous nous servirons de preUpdate(). preDqlUpdate() sera aussi utilis, lorsque la mise jour est effectue depuis une requte DQL (construite la main, reportez-vous au chapitre sur la manipulation des donnes pour plus de dtails ). Pour filtrer automatiquement les articles non publiables afin qu'ils n'apparaissent pas lors d'une slection de donnes, preDqlSelect() sera ici utile.
V oyons ds prsent le code de base de la classe : Code : PHP - ~/lib/models/templates/listeners/Doctrine_Template_Listener_Publishable.php <?php class Doctrine_Template_Listener_Publishable extends Doctrine_Record_Listener { protected $_options = array(); public function __construct(array $options) { $this->_options = $options; } public function preInsert(Doctrine_Event $event) { } public function preUpdate(Doctrine_Event $event) { } public function preDqlUpdate(Doctrine_Event $event) { } public function preDqlSelect(Doctrine_Event $event) { } protected function getCurrentUserId() { // adapter votre projet, bien sr return $_SESSION['user_id']; }
Nous avons dfini nos quatre mthodes. Nous rcuprons aussi les options du template dans l'attribut $_options car nous en aurons besoin. Chacune de ces mthodes reoit en paramtre l'objet Doctrine_Event reprsentant l'vnement. Il nous sera possible de rcuprer les objets concerns partir de celui-ci. Notez que nous savons dj que l'on va avoir besoin de connatre l'utilisateur courant (celui qui rdige et sauvegarde l'article). Le rle de la mthode getCurrentUserId() est de nous renvoyer... son ID. Bien entendu, la manire de le rcuprer va diffrer selon votre projet. vous d'adapter cela.
www.siteduzero.com
56/67
S'il y en a parmi vous qui utilisent Symfony , vous pourrez le rcuprer avec quelque chose de similaire <?php sfContext::getInstance()->getUser()->getId();.
L'objet $event est capable de nous retourner l'objet concern avec la mthode getInvoker() . Doctrine_Record possde une mthode getTable() qui nous retourne une instance de cette table (donc, la classe ArticleTable). Petit dtail La mthode Doctrine_Record::getModified() retourne la liste des champs de l'objet qui ont t modifis. Avant de mettre jour les champs concernant l'auteur, nous vrifions que ceux-ci n'ont pas t modifis manuellement (pour une quelconque raison). Si c'est le cas, alors on ne les modifie pas.
www.siteduzero.com
57/67
Enfin, si la colonne n'est pas dsactive (souvenez-vous que nous avons connaissance des options du template), on la met jour en rcuprant l'ID de l'utilisateur courant.
www.siteduzero.com
58/67
//
L'objet $event nous permet de rcuprer la requte associe avec getQuery() . $params contient... des paramtres ! En l'occurrence, nous nous intressons $params['alias'] qui contient l'alias utilis dans la requte (que j'appelle souvent 'a'). Notez par la mme occasion que l'alias principal de la requte peut aussi tre rcupr avec <?php $q>getRootAlias(). Pour nous assurer que le champ n'a pas t modifi la main, et ne pas craser les modifications, nous vrifions que la requte ne le contient pas avec Doctrine_Query::contains() . Si tout est bon, alors on peut l'ajouter notre requte avec set() . V oil, ce stade, chaque fois que l'on crera/modifiera un article, le nom de l'auteur original et du dernier l'avoir modifi seront automatiquement renseigns. Magique, non ?
Slection d'articles
Reste un dtail. Nous ne voulons pas qu'un article marqu comme supprim apparaisse lorsque nous effectuons des slections dans la base. Il nous faut pour a modifier la requte la vole. Utilisons preDqlSelect() : Code : PHP - ~/lib/models/templates/listeners/Doctrine_Template_Listener_Publishable.php <?php class Doctrine_Template_Listener_Publishable extends Doctrine_Record_Listener { /* ... */ public function preDqlSelect(Doctrine_Event $event) { // Si cette colonne n'est pas dsactive : if(!$this->_options['publishable']['disabled']) { // On rcupre ce dont on aura besoin. $q = $event->getQuery(); $params = $event->getParams(); $field = $params['alias'].'.'.$this>_options['publishable']['name']; // On vrifie qu'il faille appliquer la restriction. if(!$q->contains($field)) { $q->andWhere($field.' = ?', true);
www.siteduzero.com
59/67
} }
/* ... */
Premirement, nous nous assurons que la colonne est active, et nous rcuprons les objets dont nous avons besoin. Ensuite, on s'assure que la requte ne contient pas dj ce champ, et on ajoute une condition sur celui-ci. Et bien, chers amis, j'ai une bonne nouvelle, notre listener est termin !
Conclusion
Notre template ainsi que son listener sont maintenant oprationnels.
Rsum
Je vous donne ci-dessous le code complet du template et du listener. Oh que je suis gentil ! Doctrine_Template_Publishable : Secret (cliquez pour afficher) Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { protected $_options = array( 'created' => array( 'name' => 'created_by', 'alias' => 'OriginalAuthor',
www.siteduzero.com
60/67
true), );
), 'updated' => array( 'name' => 'updated_by', 'alias' => 'Author', 'class' => 'User', 'foreign' => 'id', 'type' => 'integer', 'disabled' => false, 'options' => array(), ), 'publishable' => array( 'name' => 'is_publishable', 'alias' => null, 'type' => 'boolean', 'disabled' => false, 'options' => array('notnull' => true, 'default' => ),
public function setTableDefinition() { if(!$this->_options['created']['disabled']) { $this->hasColumn( $this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options'] ); } if(!$this->_options['updated']['disabled']) { $this->hasColumn( $this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options'] ); } if(!$this->_options['publishable']['disabled']) { $name = $this->_options['publishable']['name']; if($this->_options['publishable']['alias']) { $name .= ' as '.$this>_options['publishable']['alias']; } $this->hasColumn( $name, $this->_options['publishable']['type'], null, $this->_options['publishable']['options'] );
$this->addListener(new Doctrine_Template_Listener_Publishable($this->_options)); } public function setUp() { if(!$this->_options['created']['disabled']) { $name = $this->_options['created']['class']; if($this->_options['created']['alias']) { $name .= ' as '.$this-
www.siteduzero.com
61/67
if(!$this->_options['updated']['disabled']) { $name = $this->_options['updated']['class']; if($this->_options['updated']['alias']) { $name .= ' as '.$this>_options['updated']['alias']; } $this->hasOne($name, array( 'local' => $this->_options['updated']['name'], 'foreign' => $this->_options['updated']['foreign'] ));
Doctrine_Template_Listener_Publishable : Secret (cliquez pour afficher) Code : PHP - ~/lib/models/templates/listeners/Doctrine_Template_Listener_Publishable.php <?php class Doctrine_Template_Listener_Publishable extends Doctrine_Record_Listener { protected $_options = array(); public function __construct(array $options) { $this->_options = $options; } public function preInsert(Doctrine_Event $event) { $invoker = $event->getInvoker(); $invokerTable = $invoker->getTable(); $modifiedFields = $invoker->getModified(); if(!$this->_options['created']['disabled']) { $createdField = $invokerTable->getFieldName($this>_options['created']['name']); if(!isset($modifiedFields[$createdField])) { $invoker->$createdField = $this>getCurrentUserId(); } } if(!$this->_options['updated']['disabled']) { $updatedField = $invokerTable->getFieldName($this>_options['updated']['name']); if(!isset($modifiedFields[$updatedField])) { $invoker->$updatedField = $this>getCurrentUserId(); } }
www.siteduzero.com
62/67
public function preDqlSelect(Doctrine_Event $event) { if(!$this->_options['publishable']['disabled']) { $q = $event->getQuery(); $params = $event->getParams(); $field = $params['alias'].'.'.$this>_options['publishable']['name']; if(!$q->contains($field)) { $q->andWhere($field.' = ?', true); }
protected function getCurrentUserId() { // adapter votre projet, bien sr. return $_SESSION['user_id']; }
Et maintenant ?
Ce TP est bien sr un exemple parmi tant d'autres. Il y a notamment une notion que nous n'avons pas abord : le partage de mthodes. V ous vous souvenez ? Maintenant, vous de jouer pour trouver des ides afin d'amliorer ce p'tit behavior. V ous avez pu apercevoir dans ce chapitre la grande modularit qu'offre Doctrine et l'intrt des templates. Attention cependant, ne les utilisez pas tort et travers ! Et souvenez-vous aussi que vous pouvez tout fait ajouter un listener sur un objet en particulier, et non obligatoirement sur une classe entire !
www.siteduzero.com
63/67
www.siteduzero.com
Partie 3 : Annexes
64/67
Partie 3 : Annexes
Personnalisation
Doctrine nous permet de personnaliser pas mal de choses dans sa configuration. Doctrine gre tout cela en Cascade, c'est--dire que l'on peut personnaliser certains aspects plusieurs niveaux. Le niveau le plus global est au niveau de Doctrine_Manager. Mais, bien entendu, il est possible de grer tout cela plus finement. Par exemple, nous pouvons rcuprer les rsultats de toutes les requtes de diffrentes manires. On peut dfinir cela globalement ; par exemple, on choisit de rcuprer des objets complets chaque fois. Mais il reste tout fait possible, au niveau de la requte elle-mme par exemple, de dire Cette fois, je veux juste rcuprer un tableau contenant certaines proprits, et non l'objet en entier . Notez que je rdigerai prochainement un chapitre spcifique sur l'Hydratation des rsultats.
Avec Doctrine_Manager
Doctrine_Manager possde une mthode setAttribute() qui permet de changer certains... attributs !
Comment a marche ?
Tout simplement, en appelant la mthode cite : Code : PHP <?php $manager = Docrine_Manager::getInstance(); $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL); $manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
Les attributs possibles sont dfinis dans Doctrine_Core et sont de la forme ATTR_ NOM_DE_LATTRIBUT . Leurs valeurs possibles sont des constantes de la forme NOM_DE_LATTRIBUT _ VALEUR .
Cet attribut indique la manire dont Doctrine va valider les donnes ; par exemple, si l'on doit forcer un champ dclar de type integer contenir bel et bien un nombre entier. Les valeurs disponibles sont : V ALIDATE_ALL (correspond l'ensemble des validateurs suivants) ; V ALIDATE_LENGTHS ; V ALIDATE_TYPES ; V ALIDATE_CONSTRAINTS ; V ALIDATE_NONE.
Notez que l'on peut combiner plusieurs valeurs avec l'oprateur bit--bit | . Par exemple :
www.siteduzero.com
Partie 3 : Annexes
Code : PHP <?php $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_LENGTHS | Doctrine_Core::VALIDATE_TYPES);
65/67
ATTR_AUTO_ACCESSOR_OVERRIDE
Autorise ou non la redfinition des accesseurs, c'est--dire, la possibilit ou non de crer une mthode getProperty() pour accder la colonne property. L'intrt est de pouvoir effectuer des traitements avant de renvoyer la valeur. Valeurs possibles : true ou false.
ATTR_AUTOLOAD_TABLE_CLASSES
Indique si Doctrine doit ou non charger les classes de table (ArticleTable par exemple) automatiquement. Valeurs possibles : true ou false. Je vous recommande de le laisser true si vous les utilisez. Dans le cas contraire, cela amliorera (lgrement...) les performances.
ATTR_MODEL_LOADING
Indique la manire dont Doctrine va charger les modles. Les valeurs possibles sont : MODEL_LOADING_AGGRESSIVE : Doctrine inclut TOUS les modles trouvs, mme ceux que vous n'utilisez pas. MODEL_LOADING_CONSERV ATIVE : stocke simplement le chemin de chaque fichier contenant un modle, et l'inclu uniquement SI BESOIN. MODEL_LOADING_PEAR : ne prcharge pas les modles, vrifie chaque appel dans le dossier indiqu loadModels() (souvenez-vous ) si le modle existe.
ATTR_USE_DQL_CALLBACKS
Cet attribut indique si oui ou non les hooks DQL doivent tre appels. Reportez-vous au chapitre sur les Events Listeners pour plus de dtails sur ces hooks. Valeurs possibles : true ou false. Attention, cet attribut est false par dfaut. Ceci signifie que vous devez explicitement le dfinir true, quelque part dans votre application, pour utiliser les hooks DQL.
ATTR_QUOTE_IDENTIFIER
Cet attribut indique Doctrine qu'il doit encapsuler tous les noms de tables, champs, etc. dans des dlimiteurs. Pour info, MySQL utilise les backticks (`), Oracle utilise les guillemets doubles ("), Access utilise les crochets ([ et ]), etc. Attention. N'utilisez jamais ces dlimiteurs en dur dans votre code. Cela CASSE la portabilit de votre application, ce qui est justement tout l'inverse de ce que l'on veut avec Doctrine !
Valeurs possibles : true ou false. Je vous conseille de le mettre true uniquement si vous avez des problmes lors de l'excution de requtes.
www.siteduzero.com
Partie 3 : Annexes
Ce tutoriel n'est pas termin !!!
66/67
www.siteduzero.com