268920-apprendre-a-utiliser-doctrine


268920-apprendre-a-utiliser-doctrine

 

Apprendre utiliser Doctrine Par Cybermanu www.openclassrooms.com Licence Creative Commons 6 2.0 Dernire mise jour le 15/02/2011 Sommaire 2 Sommaire ........................................................................................................................................... 2 Partager .............................................................................................................................................. 4 Apprendre utiliser Doctrine .............................................................................................................. 4 Connaissances requises ................................................................................................................................................... 4 Remerciements ................................................................................................................................................................. 5 Partie 1 : Dbuter avec Doctrine ......................................................................................................... 5 Conventions utilises ........................................................................................................................................................ 5 Prsentation et installation ................................................................................................................................................ 5 Doctrine, c'est quoi ? ................................................................................................................................................................................................... 6 Installation ................................................................................................................................................................................................................... 6 Tlchargement .......................................................................................................................................................................................................... 7 Implmentation dans notre projet ................................................................................................................................................................................ 9 Configuration de la base de donnes ............................................................................................................................... 9 Connexion ................................................................................................................................................................................................................... 9 La classe Doctrine_Manager ....................................................................................................................................................................................... 9 Connexion la base de donnes ................................................................................................................................................................................ 9 Schma de donnes (Yaml) ........................................................................................................................................................................................ 9 Le langage Yaml .......................................................................................................................................................................................................... 10 Le schma ................................................................................................................................................................................................................. 11 Schma de donnes (PHP) ....................................................................................................................................................................................... 11 Les classes de modle .............................................................................................................................................................................................. 12 Les classes reprsentant les tables .......................................................................................................................................................................... 13 Gnration des tables et modles ............................................................................................................................................................................ 15 Manipulation des donnes stockes dans la base ......................................................................................................... 15 Utilisation des objets et collections ........................................................................................................................................................................... 15 Utilisation des objets ................................................................................................................................................................................................. 17 Utilisation des collections d'objets ............................................................................................................................................................................. 18 Le langage DQL ........................................................................................................................................................................................................ 18 Slection simple de donnes .................................................................................................................................................................................... 19 SELECT : Doctrine_Query en dtails ........................................................................................................................................................................ 21 UPDATE et DELETE ................................................................................................................................................................................................. 22 Utilit des classes de tables ...................................................................................................................................................................................... 25 TP : Cration d'un CRUD ................................................................................................................................................ 25 Prparation du terrain ................................................................................................................................................................................................ 25 Rcapitulatif .............................................................................................................................................................................................................. 25 Le contrleur ............................................................................................................................................................................................................. 26 Donnes de test ........................................................................................................................................................................................................ 26 Affichage de donnes ................................................................................................................................................................................................ 27 Cration de la requte ............................................................................................................................................................................................... 27 Affichage des donnes rcupres ........................................................................................................................................................................... 28 Cration d'objets ....................................................................................................................................................................................................... 28 Le formulaire ............................................................................................................................................................................................................. 28 Traitement des informations ...................................................................................................................................................................................... 29 Modification d'objets .................................................................................................................................................................................................. 29 Le formulaire ............................................................................................................................................................................................................. 29 Traitement des informations ...................................................................................................................................................................................... 30 Refactorisation du code ............................................................................................................................................................................................ 31 Suppression de donnes .......................................................................................................................................................................................... 31 Traitement ................................................................................................................................................................................................................. 33 Partie 2 : Utilisation avance de Doctrine ......................................................................................... 33 Les Events Listeners ....................................................................................................................................................... 33 Introduction aux Events Listeners ............................................................................................................................................................................. 33 Dclenchement d'vnements .................................................................................................................................................................................. 33 Intercepter un vnement ......................................................................................................................................................................................... 33 Les mthodes utilises .............................................................................................................................................................................................. 34 Doctrine_Event .......................................................................................................................................................................................................... 35 Listener de connexion ............................................................................................................................................................................................... 35 Crer un listener ........................................................................................................................................................................................................ 36 Attacher un listener ................................................................................................................................................................................................... 37 Listeners d'objets ...................................................................................................................................................................................................... 37 Crer un listener ........................................................................................................................................................................................................ 37 Attacher un listener ................................................................................................................................................................................................... 38 Les hooks .................................................................................................................................................................................................................. 38 Intercepter les requtes ............................................................................................................................................................................................. 39 Informations supplmentaires ................................................................................................................................................................................... 39 Interrompre l'action en cours ..................................................................................................................................................................................... 40 Ne pas excuter le listener suivant ........................................................................................................................................................................... 41 Les templates .................................................................................................................................................................. 41 Introduction ................................................................................................................................................................................................................ 41 Qu'est-ce qu'un template ? ........................................................................................................................................................................................ 41 Et Doctrine dans tout a ? ......................................................................................................................................................................................... 42 Utilisation des templates ........................................................................................................................................................................................... 42 Dcouverte des behaviors ........................................................................................................................................................................................ 2/67 www.openclassrooms.com 43 Timestampable .......................................................................................................................................................................................................... 45 SoftDelete .................................................................................................................................................................................................................. 46 Versionable ................................................................................................................................................................................................................ 47 Sluggable .................................................................................................................................................................................................................. 47 Structure d'un template ............................................................................................................................................................................................. 47 Comment a marche ? .............................................................................................................................................................................................. 48 Mthodes dlgues ................................................................................................................................................................................................. 51 TP : Cration d'un template ............................................................................................................................................. 51 Crer son propre template ........................................................................................................................................................................................ 51 Introduction ................................................................................................................................................................................................................ 51 Cration de la classe ................................................................................................................................................................................................. 51 Dtails supplmentaires ............................................................................................................................................................................................ 52 Ajout d'un listener ...................................................................................................................................................................................................... 52 Correction : le template ............................................................................................................................................................................................. 52 Les options ................................................................................................................................................................................................................ 53 Les colonnes ............................................................................................................................................................................................................. 54 Les relations .............................................................................................................................................................................................................. 55 Correction : le listener ............................................................................................................................................................................................... 56 Pr-insertion d'un article ............................................................................................................................................................................................ 57 Mise jour d'un article .............................................................................................................................................................................................. 58 Slection d'articles .................................................................................................................................................................................................... 59 Conclusion ................................................................................................................................................................................................................ 63 Partie 3 : Annexes ............................................................................................................................. 64 Personnalisation .............................................................................................................................................................. 64 Avec Doctrine_Manager ............................................................................................................................................................................................ 64 Comment a marche ? .............................................................................................................................................................................................. 64 Dtail des principaux attributs ................................................................................................................................................................................... Sommaire 3/67 www.openclassrooms.com Apprendre utiliser Doctrine Par Cybermanu Mise jour : 15/02/2011 Difficult : Intermdiaire 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. Sommaire 4/67 www.openclassrooms.com Partie 1 : Dbuter avec Doctrine Dans cette premire partie, on va dcouvrir ce qu'est Doctrine, et commencer l'utiliser tout en douceur. 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. Doctrine, c'est quoi ? Doctrine est un ORM, pour object-relational mapping, soit mapping objet-relationnel en franais. Attends, euh... c'est quoi un ORM ??? 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 ceuxqui 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(); Apprendre utiliser Doctrine 5/67 www.openclassrooms.com Et voil ! En quelques lignes, nous avons cr un objet article, l'avons rempli avec des donnes, et l'avons sauvegard. Et c'est tout. Doctrine se charge tout seul d'insrer ces donnes dans les champs de notre table, on ne touche pas au code SQL. 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 deuxcouches 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/. Partie 1 : Dbuter avec Doctrine 6/67 www.openclassrooms.com Placez le contenu du dossier <Archive>/Doctrine-1.2.2/lib/ que vous venez de tlcharger, dans ~/lib/vendor/doctrine/ (que vous crez pour l'occasion ). 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. Implmentation dans notre projet Doctrine peut inclure les fichiers contenant nos classes automatiquement, nous n'aurons pas faire d'include() ou require() manuellement. Pour cela, il va falloir ajouter quelques lignes de configuration votre projet. Crez un fichier ~/config/global.php. Tout d'abord, dfinissons quelques constantes et incluons la classe de base de Doctrine : Code : PHP - ~/config/global.php <?php // Adaptez bien sr le DSN votre cas define('CFG_DB_DSN', 'mysql://root@localhost/db_doctrine_test'); define('LIB_DIR', dirname(__FILE__).'/../lib/'); define('CFG_DIR', dirname(__FILE__).'/'); define('WEB_DIR', dirname(__FILE__).'/../web/'); define('HTML_DIR', dirname(__FILE__).'/../html/'); require_once(LIB_DIR.'vendor/doctrine/Doctrine.php'); spl_autoload_register(array('Doctrine_Core', 'autoload')); 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 curieuxet 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 Partie 1 : Dbuter avec Doctrine 7/67 www.openclassrooms.com respecter, et pour ne pas compliquer la comprhension de ceuxqui 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(); Si le chemin vers Doctrine s'affiche, c'est bon. Sinon, reprenez depuis le dbut, mais il ne devrait pas y avoir de problme ce stade-l. Rappelez-vous bien que le fichier global.phpdevra 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. Partie 1 : Dbuter avec Doctrine 8/67 www.openclassrooms.com Configuration de la base de donnes Nous allons maintenant voir comment nous connecter avec la base de donnes, puis configurer nos tables. Connexion La classe Doctrine_Manager Toutes les connexions la base de donnes sont gres par un objet Doctrine_Manager. Doctrine_Manager est un singleton, cela signifie qu'il ne peut y en avoir qu'une seule instance la fois dans votre projet. Si vous tes alls farfouiller dans ~/lib/vendor/doctrine/Doctrine/Manager.php (ce que je vous encourage toujours faire ), vous avez pu vous apercevoir que la mthode __construct() est prive : on ne pourra donc pas faire de <?php new Doctrine_Manager(); ?>. Mais alors, pourquoi tu nous parles de cette classe si elle ne sert rien ? 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. Connexion la base de donnes Doctrine_Manager possde une mthode statique connection(). Cette mthode prend en paramtre un DSN, du mme format que PDO (par exemple, mysql://root:@localhost/db_doctrine_test) et nous retourne un objet connexion. Un DSN doit tre de la forme SGBD://utilisateur:motdepasse@serveur/nomdelabasededonnes. Modifions le fichier ~/config/global.php : Code : PHP - ~/config/global.php <?php // Adaptez bien sr le DSN votre cas. define('CFG_DB_DSN', 'mysql://root@localhost/db_doctrine_test'); define('LIB_DIR', dirname(__FILE__).'/../lib/'); define('CFG_DIR', dirname(__FILE__).'/'); define('WEB_DIR', dirname(__FILE__).'/../web/'); define('HTML_DIR', dirname(__FILE__).'/../html/'); require_once(LIB_DIR.'vendor/doctrine/Doctrine.php'); spl_autoload_register(array('Doctrine_Core', 'autoload')); $conn = Doctrine_Manager::connection(CFG_DB_DSN); Note : Doctrine_Manager peut prendre en charge plusieurs connexions diffrentes bases de donnes. Il est conseill pour cela d'affecter explicitement un label auxconnexions cres : Doctrine_Manager::connection('dsn', 'label');. Il est possible ensuite de les rcuprer avec Doctrine_Manager::getConnection('label');. Schma de donnes (Yaml) Petite surprise : on va apprendre un nouveau langage. Mais pas de panique, c'est un langage trs simple, utilis pour dfinir le schma de donnes de Doctrine. Partie 1 : Dbuter avec Doctrine 9/67 www.openclassrooms.com 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 UNIQUEMENTAVEC 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. Ces rgles sont trs intuitives, voyez par vous-mmes. 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 Partie 1 : Dbuter avec Doctrine 10/67 www.openclassrooms.com 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. Schma de donnes (PHP) 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. Les classes de modle 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 Partie 1 : Dbuter avec Doctrine 11/67 www.openclassrooms.com du nom de la classe, donc il est inutile // de prciser 'as User'. } } 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. L'hritage des classes de base Lisez bien ceci, c'est important. V ous avez vu que nous avons appel nos classes BaseArticle et BaseUser et que nous les avons places dans le dossier generated/. Ceci est peu important si vous crivez le PHP la main, et vous pouvez directement nommer vos classes Article et User. Je le fais pour que dans la suite du tuto, la configuration du projet soit la mme, peu importe la mthode que vous avez choisie. Je ne comprends toujours pas l'utilit de ces classes BaseMachin moi... Lorsque nous demanderons Doctrine de les gnrer automatiquement partir du Yaml, Doctrine aura besoin de recrer ces fichiers. Pour ne pas effacer ceuxqui 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. Les classes reprsentant les tables Si vous dcidez d'utiliser le PHP, il vous faudra aussi crer une classe pour chaque table. Elles nous serviront plus tard pour effectuer certaines tches. Code : PHP - ~/lib/models/ArticleTable.phpet ~/lib/models/UserTable.php <?php // Fichier ~/lib/models/ArticleTable.php class ArticleTable extends Doctrine_Table { } Partie 1 : Dbuter avec Doctrine 12/67 www.openclassrooms.com // Fichier ~/lib/models/UserTable.php class UserTable extends Doctrine_Table { } 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. Gnration des tables et modles V oyons maintenant comment gnrer automatiquement notre base de donnes partir du schma que nous venons de crer. Modifions le fichier ~/config/global.php pour correspondre nos besoins : Code : PHP - ~/config/global.php <?php // Adaptez bien sr le DSN votre cas. define('CFG_DB_DSN', 'mysql://root@localhost/db_doctrine_test'); define('LIB_DIR', dirname(__FILE__).'/../lib/'); define('CFG_DIR', dirname(__FILE__).'/'); define('WEB_DIR', dirname(__FILE__).'/../web/'); define('HTML_DIR', dirname(__FILE__).'/../html/'); 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 : souvenez- vous 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 auxchapitres prcdents. V oici les commandes excuter pour que Doctrine gnre les fichiers de modle et construise notre base de donnes : Partie 1 : Dbuter avec Doctrine 13/67 www.openclassrooms.com 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 auxautres 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... ) Partie 1 : Dbuter avec Doctrine 14/67 www.openclassrooms.com Manipulation des donnes stockes dans la base On y arrive enfin ! J'ai pris le temps de bien vous montrer comment fonctionne Doctrine, car cela me parat important pour la suite. Ce n'est pas primordial pour l'instant, mais comme je l'ai dit, nous explorerons plus en dtail le fonctionnement de Doctrine plus loin dans le tuto. Je commence donc prparer le terrain. On va maintenant pouvoir crer des objets, les modifier, les sauvegarder... Cool, n'est-ce pas ? V oyons cela tout de suite. Ne cherchez pas forcment tester le code qui suit tout de suite. Le prochain chapitre est consacr un TP qui vous permettra de mettre tout a en pratique. Utilisation des objets et collections V ous vous rappelez l'exemple que je vous avais donn au dbut ? Et bien, on va utiliser exactement cela. Utilisation des objets Crer un objet Commenons par crer un nouvel objet : Code : PHP <?php $article = new Article(); Doctrine nous fournit plusieurs manires d'accder auxproprits 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(). Dfinissons maintenant l'auteur de l'article : Code : PHP Partie 1 : Dbuter avec Doctrine 15/67 www.openclassrooms.com <?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(); N'oubliez pas de sauvegarder ; sans a, la base de donnes ne sera pas modifie ! 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. Partie 1 : Dbuter avec Doctrine 16/67 www.openclassrooms.com En fait, Doctrine utilise trs largement une mthode magique de PHP : __call(). PHP appelle cette mthode lorsque nous en utilisons une qui n'est pas dfinie. Ensuite, c'est l'intrieur de cette mthode que tout le travail est fait. 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. Utilisation des collections d'objets l'instant, nous avons utilis des mthodes nommes findOneBy..., qui nous retournent un seul objet. Mais si plusieurs objets peuvent correspondre auxcritres, alors il est prfrable d'utiliser findBy... qui retourne un objet Doctrine_Collection contenant une liste d'objets. Ces collections peuvent s'utiliser comme des tableaux. noter qu'il existe une mthode findAll() qui retourne TOUS les enregistrements de la table.Attention en l'utilisant sur des tables qui contiennent beaucoup d'enregistrements... V oyons un exemple : Code : PHP <?php $table = Doctrine_Core::getTable('Article'); $articles = $table->findByTitle('Titre'); foreach($articles as $article) { echo $article->title; } 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. Partie 1 : Dbuter avec Doctrine 17/67 www.openclassrooms.com 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. Slection simple de donnes Rassurez-vous, vous n'allez pas devoir apprendre (encore !) un nouveau langage ultra complexe... Le DQL utilise en fait les mmes instructions que le SQL. Pour ce premier chapitre, nous allons donc utiliser le mot cl... SELECT ! Tout d'abord, petite explication. Lorsque vous slectionnez un objet de cette manire : Code : PHP <?php $article = Doctrine_Core::getTable('Article')->find(1); 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. Partie 1 : Dbuter avec Doctrine 18/67 www.openclassrooms.com Euh... C'est quoi l'intrt de faire a alors qu'on peut le rcuprer en une seule ligne, notre article ?! 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 en dtails Passons maintenant en revue les diffrentes mthodes disponibles pour construire notre Doctrine_Query. 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 = ? OR a.id = ?', array(1, 2)); $q->orWhere('a.title = ?', array('Mon titre')); // Ou : $q->orWhere('a.title = ?', 'Mon titre'); // Ou encore : $q->orWhere('a.title = :title', array(':title' => 'Mon titre')); JOIN Doctrine supporte deuxtypes 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 Partie 1 : Dbuter avec Doctrine 19/67 www.openclassrooms.com 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 ceuxqui 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(). Rcupration des rsultats Pour finir, la requte doit tre excute pour pouvoir rcuprer les rsultats. Doctrine possde plusieurs mthodes pour cela : execute() : cette mthode retourne un objet Doctrine_Collection contenant un enregistrement ou plus. Notez que vous pouvez passer en argument un tableau contenant les paramtres n'ayant pas encore t renseigns. On peut galement passer en deuxime argument le mode d'hydratation des rsultats, mais nous verrons cela plus tard. fetchOne() : cette mthode retourne un objet Doctrine_Record. Cette mthode accepte les mmes arguments que execute(). Il existe d'autres mthodes qui retournent des rsultats, telle que fetchArray(). Mais nous verrons cela plus tard (oui, encore ). Note sur le passage des paramtres V ous avez vu que les mthodes acceptant des variables peuvent recevoir en paramtre ces valeurs remplir. Lorsque vous utilisez des valeurs dynamiques comme cela, vous devez toujours les passer en paramtre. De cette manire, les caractres dangereuxseront chapps automatiquement, vitant des injections SQL. Ces paramtres s'utilisent de la mme manire qu'avec PDO. Deuxchoixs'offrent vous pour indiquer leur emplacement : Utiliser des ?. Dans ce cas, les paramtres doivent tre passs dans le mme ordre que les ?. Par exemple : <?php $q->where('a.title = ?', $title). Partie 1 : Dbuter avec Doctrine 20/67 www.openclassrooms.com Utiliser des paramtres nomms, comme :name. Dans ce cas, l'argument supplmentaire doit tre un tableau associatif de paires nom => valeur. Par exemple : <?php $q->where('a.title = :title', array(':title' => $title)). 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 deuxcodes 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) Partie 1 : Dbuter avec Doctrine 21/67 www.openclassrooms.com ->limit(20); $number = $q->execute(); 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. Utilit des classes de tables Au chapitre prcdent, je vous avais dit de crer, en plus des classes classiques de modles, des classes de tables (par exemple ArticleTable.class.php). Mais peut-tre n'en voyez-vous pas l'utilit. En fait, nous allons nous en servir pour effectuer des traitements lorsqu'il y en aura besoin. Tout le code (ou du moins le plus possible ) concernant des actions sur la table et ses donnes devra tre plac dans cette classe. Prenons un exemple. Imaginons que nos articles contiennent un champ is_publishable qui indique si l'article est publiable ou non. Par dfaut, la mthode find() cherche un article avec son id. Mais nous voulons en plus une scurit , c'est--dire ne pas pouvoir rcuprer les articles non publiables, par exemple. Pour cela, nous allons redfinir cette mthode : Code : PHP <?php class ArticleTable extends Doctrine_Table { public function find($id) { $q = $this->createQuery('a') ->where('a.id = ?', $id) ->andWhere('a.is_publishable = ?', true); return $q->fetchOne(); } } 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 : Partie 1 : Dbuter avec Doctrine 22/67 www.openclassrooms.com Code : PHP <?php Doctrine_Query::create() ->from('Article a') 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_Collection ) possde une mthode flush(), qui permet de sauvegarder tout ce qui a t modifi avec la connexion courante. C'est utile lorsque vous Partie 1 : Dbuter avec Doctrine 23/67 www.openclassrooms.com modifiez un grand nombre d'objets et collections diffrents : cela vous vite de sauvegarder les objets/collections un par un. Partie 1 : Dbuter avec Doctrine 24/67 www.openclassrooms.com TP : Cration d'un CRUD Nous allons maintenant passer un peu plus la pratique et crer un CRUD. Pour ceuxqui ne connaissent pas, un CRUD est un systme de manipulation des donnes de la base : a signifie Create, Read, Update, Delete (Crer, Lire, Mettre jour, Supprimer). Autrement dit, on va faire une interface pour grer nos donnes. Cela peut entre autres servir crer une interface d'administration pour votre site. Attention, ce TP est l uniquement pour vous faire pratiquer un peu sur des choses simples, pour commencer retenir un peu le fonctionnement de Doctrine et les mthodes utiles. Je ne traiterai pas, notamment, de la scurit des scripts. Ceci est intgrer un projet, c'est pourquoi je vous donne un contrleur frontal si basique. Prparation du terrain Rcapitulatif Avant de commencer, rcapitulons la structure de notre projet. Jusqu' maintenant je vous ai simplement indiqu o placer quelques fichiers. Nous utiliserons cette structure : ~/config/ ----global.php ----schema.yml ~/html/ ~/lib/ ----models/ --------Article.php --------ArticleTable.php --------User.php --------UserTable.php --------generated/ ------------BaseArticle.php ------------BaseUser.php ~/web/ ----index.php Si vous avez suivi le tutoriel en entier, vous ne devriez rien avoir faire. Dans le cas contraire, je vous laisse vous reporter aux chapitres prcdents pour connatre le contenu de ces fichiers. Comme je vous l'ai dit, nous allons grandement simplifier le modle MVC : nous aurons un contrleur frontal (~/web/index.php) qui se chargera aussi d'inclure la page HTML. Le but ici n'est pas d'expliquer le pattern MVC. Libre vous d'utiliser votre propre systme de templates/framework, d'adapter tout cela un projet existant, etc. Je vous encourage d'ailleurs le faire. 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. ^^ Partie 1 : Dbuter avec Doctrine 25/67 www.openclassrooms.com $action = 'read'; if(isset($_GET['action']) && in_array($_GET['action'], array('create', 'read', 'update', 'delete'))) { $action = $_GET['action']; } /* Nous ferons ici les traitements concernant la page. */ switch($action) { case 'read': break; case 'create': break; case 'update': break; case 'delete': break; } /* Nous appellerons ici la page HTML approprie. */ include(HTML_DIR.$action.'.php'); 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. Partie 1 : Dbuter avec Doctrine 26/67 www.openclassrooms.com 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. Affichage des donnes rcupres Crons maintenant la vue pour l'action read : Code : PHP - ~/html/read.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> <head> <title>Tutoriel Doctrine</title> <meta http-equiv="content-type" content="text/html; charset=iso- 8859-1" /> </head> <body> <h1>Liste des articles</h1> <a href="?action=create">Ajouter un nouvel article</a> <table> <tr> <th>Titre</th> <th>Contenu</th> <th>Auteur</th> <th><em>Action</em></th> </tr> <?php foreach($articles as $article): ?> <tr> <td><?php echo $article['title'] ?></td> <td><?php echo $article['content'] ?></td> <td><?php echo $article['User']['login'] ?></td> <td> <a href="?action=update&amp;id=<?php echo $article['id'] ? >">Modifier</a> <a href="?action=delete&amp;id=<?php echo $article['id'] ? >">Supprimer</a> </td> </tr> <?php endforeach; ?> </table> </body> </html> 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), Partie 1 : Dbuter avec Doctrine 27/67 www.openclassrooms.com nous utilisons foreach pour les parcourir. 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 nouveauxobjets 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. Traitement des informations Code : PHP - ~/web/index.php <?php /* ... */ case 'create': if(!empty($_POST)) { $article = new Article(); $article->title = $_POST['title']; $article->content = $_POST['content']; // On indique l'auteur. Adaptez cela votre projet. $article->User = $user; $article->save(); } break; /* ... */ Partie 1 : Dbuter avec Doctrine 28/67 www.openclassrooms.com Nous rcuprons ici les donnes postes par le formulaire, et les affectons un nouvel article, puis nous le sauvegardons. 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> Traitement des informations Code : PHP - ~/web/index.php <?php /* ... */ case 'update': $id = isset($_POST['id']) ? $_POST['id'] : $_GET['id']; // Il faut videmment s'assurer que l'article existe if(!($article = Doctrine_Core::getTable('Article')->find($id))) { // vous de mettre un traitement personnalis ! // Par exemple, on considre qu'il s'agit d'un nouvel article. $article = new Article(); // Ou bien : exit(); } 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(); Partie 1 : Dbuter avec Doctrine 29/67 www.openclassrooms.com } break; /* ... */ 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> Partie 1 : Dbuter avec Doctrine 30/67 www.openclassrooms.com 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 /* ... */ Partie 1 : Dbuter avec Doctrine 31/67 www.openclassrooms.com case 'delete': $id = isset($_POST['id']) ? $_POST['id'] : $_GET['id']; // On s'assure que l'article existe if($article = Doctrine_Core::getTable('Article')->find($id)) { $article->delete(); } break; /* ... */ 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. Partie 1 : Dbuter avec Doctrine 32/67 www.openclassrooms.com Partie 2 : Utilisation avance de Doctrine Maintenant que vous en connaissez un minimum sur Doctrine, on va pouvoir s'attaquer des choses vraiment intressantes. Les Events Listeners Dans cette partie, nous nous intresserons auxEvents Listeners. Comme leur nom l'indique, ces choses bizarres permettent... d'couter des vnements ! Non, je ne vous prends pas pour des billes, nous allons voir immdiatement de quoi il s'agit. De manire gnrale et thorique, lorsqu'un vnement est dclench, les listeners associs en sont informs, et peuvent agir en consquence. Ils peuvent non seulement faire leur petit travail de leur ct, mais aussi, et c'est trs important, modifier l'excution de l'vnement. Petite prcision avant de commencer : cette partie restera assez thorique, mais nous (c'est--dire vous ) mettrons tout cela en pratique dans un prochain TP. Introduction aux Events Listeners Dclenchement d'vnements La premire chose connatre, lorsque l'on parle d'vnements, est de savoir quand ils sont dclenchs. Dans Doctrine, nous ne nous occuperons pas de les dclencher, cela est fait automatiquement. Par exemple, lors d'un enregistrement dans la base de donnes, ou lors d'une slection de donnes, etc. Pour ceuxqui utilisent Symfony ou qui ont l'habitude de dclencher des vnements manuellement, gardez bien l'esprit qu'avec Doctrine, nous ne parlons que d'Event Listener, ce qui signifie que nous ne faisons que les couter. Le framework Symfony (entre autres) intgre une gestion complte des vnements, ce qui vous permet d'en dclencher vous-mme. Ce n'est pas le cas ici ! 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 mieuxtout 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. Les mthodes utilises Les mthodes (que ce soient des hooks, ou que ce soient celles des listeners) que nous allons utiliser seront expliques par la Partie 1 : Dbuter avec Doctrine 33/67 www.openclassrooms.com suite. Leur utilisation est intuitive, voyons un exemple : Code : PHP <?php class Article extends Doctrine_Record { public function preSave($event) { $this->user_id = $_SESSION['user_id']; } } 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() Partie 2 : Utilisation avance de Doctrine 34/67 www.openclassrooms.com Retourne l'objet Doctrine_Query associ l'vnement, s'il existe. Attention, il peut ne pas exister ! Dans l'exemple prcdent, cela renverra la valeur null. 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 auxlisteners 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. tendre une classe de base Nous appellerons notre listener fictif CustomListener. Doctrine_EventListener offrant une base pour tout listener, le ntre en hritera donc : Code : PHP <?php class CustomListener extends Doctrine_EventListener { } Ouvrez le fichier ~/lib/vendor/doctrine/Doctrine/EventListener.php. V ous devriez maintenant mieuxcomprendre 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. tendre une autre classe Si, pour une raison quelconque, votre listener ne peut pas hriter de Doctrine_EventListener (parce qu'il hrite dj d'une autre classe, par exemple), il va falloir crire en dur chaque mthode. Explications La seule condition pour crer un listener, c'est en fait qu'il implmente l'interface Doctrine_EventListener_Interface. Comme vous avez pu le voir en ouvrant le fichier EventListener.php, la seule chose que fait la classe Doctrine_EventListener est d'implmenter cette interface. Elle dfinit toutes les mthodes.Ainsi, la classe fille (CustomListener) implmente indirectement cette mme interface, tout en n'tant pas oblige de redfinir toutes les mthodes. Donc, si vous n'utilisez pas toutes les mthodes dans votre listener, vous devrez quand mme les dfinir ! Un exemple de code sera certainement plus parlant : Code : PHP <?php class CustomListener2 extends UneAutreClasse implements Doctrine_EventListener_Interface { public function preTransactionCommit(Doctrine_Event $event) Partie 2 : Utilisation avance de Doctrine 35/67 www.openclassrooms.com { } public function postTransactionCommit(Doctrine_Event $event) { } // Et ainsi de suite avec toutes les mthodes imposes par l'interface... } 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 deuxparamtres : 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. Partie 2 : Utilisation avance de Doctrine 36/67 www.openclassrooms.com Les listeners attachs Doctrine_Connection coutent galement les classes proches, comme Doctrine_Transaction et Doctrine_Connection_Statement. Nanmoins, le listener est global ces trois classes, et doit tre attach Doctrine_Connection. 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. Partie 2 : Utilisation avance de Doctrine 37/67 www.openclassrooms.com Code : PHP <?php $connexion->addRecordListener(new CustomRecordListener()); 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()); } } Ceci nous sera extrmement utile, notamment, avec l'utilisation de templates... 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. Intercepter les requtes Il existe deuxmthodes pour intercepter les requtes : avec des hooks et dans les listeners. Partie 2 : Utilisation avance de Doctrine 38/67 www.openclassrooms.com Elles sont cependant identiques, et consistent en ces trois mthodes : preDqlSelect() est appele lors d'une requte de type SELECT ; preDqlUpdate() est appele lors d'une requte de type UPDATE ; preDqlDelete() est appele lors d'une requte de type DELETE. Leur fonctionnement est identique auxautres 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. Informations supplmentaires Interrompre l'action en cours Il est possible d'annuler totalement l'vnement, grce la mthode Doctrine_Event::skipOperation(). Code : PHP <?php class CustomRecordListener extends Doctrine_Record_Listener { public function preSave(Doctrine_Event $event) { // Vous faites ce que vous voulez ici... $event->skipOperation(); } } class Article extends Doctrine_Record { public function setUp() { $this->addListener(new CustomRecordListener()); } } Attention, avec cet exemple, vos objets ne pourront pas tre enregistrs avec la mthode save()... Partie 2 : Utilisation avance de Doctrine 39/67 www.openclassrooms.com Ne pas excuter le listener suivant Lorsque vous ajoutez plusieurs listeners une classe, il peut tre utile, lors de l'appel de l'un d'entre eux, que le suivant (celui qui a t attach juste aprs) ne soit pas appel. Ceci est permis par Doctrine_Event::skipNextListener(). Code : PHP <?php class CustomRecordListener1 extends Doctrine_Record_Listener { public function preSave(Doctrine_Event $event) { /* ... */ $event->skipNextListener(); } } 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... Partie 2 : Utilisation avance de Doctrine 40/67 www.openclassrooms.com 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. Introduction Qu'est-ce qu'un template ? De manire gnrale, en informatique, un template est un modle. Comme vous le savez, un programmeur est fainant (M@teo21 le rpte pas mal dans ses tutoriels ). Cette fainantise le pousse dtester une chose plus que tout : la duplication de code. Cette rcriture de code identique (ou presque) se retrouve dans un grand nombre de concepts diffrents lis l'informatique (ou pas, d'ailleurs ), et le principal inconvnient est une baisse de la maintenabilit de l'application. 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. Et Doctrine dans tout a ? Lien avec Doctrine Aprs ces brves explications concernant les templates, vous ne voyez peut-tre pas le rapport avec Doctrine. Cherchez bien : o pourrions-nous avoir de la duplication de code dans notre projet ? V ous ne voyez pas ? Dans notre schma pardi ! Bien souvent, dans une application relativement complte, vos objets devront tre dats. Ce peut tre le cas d'articles, de commentaires lis l'article, etc. Prenez par exemple Facebook (au hasard ), o toutes les actions sont dates : la mise en ligne de photos, publication de messages, ajout d'amis, participation des vnements... 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 ? Partie 2 : Utilisation avance de Doctrine 41/67 www.openclassrooms.com Quand dois-je utiliser les templates ? Ds qu'il y a quelque chose de commun entre plusieurs classes, utilisez-les ! V otre application n'en sera que plus simple maintenir en cas de changement. Un point commun peut tre des colonnes identiques (par exemple, une colonne date), un comportement, des relations, etc. 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 ! Utilisation des templates Doctrine propose plusieurs behaviors intgrs par dfaut. Nous allons commencer par en tudier quelques-uns afin de voir comment a marche. J'utiliserai le plus souvent le terme objet pour dfinir une instance d'une classe du modle (comme Article), et le terme table pour une instance d'une classe reprsentant une table (comme ArticleTable). Il s'agit d'un abus de langage, mais a ne vous gnera pas pour comprendre. Dcouverte des behaviors Il est trs simple d'appliquer un behavior sur nos objets. Il faut l'indiquer dans le schma de donnes. 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] # ... Partie 2 : Utilisation avance de Doctrine 42/67 www.openclassrooms.com 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 ceuxqui 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 deuxnouvelles 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 # ... Partie 2 : Utilisation avance de Doctrine 43/67 www.openclassrooms.com V oici la correspondance en PHP : Code : PHP <?php /* ... */ $this->actAs('Timestampable', array( 'created' => array( 'disabled' => true ), 'updated' => array( 'name' => 'updated_date', 'onInsert' => false, 'format' => 'Y-m-d', ) )); /* ... */ 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 deuxchamps (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. Partie 2 : Utilisation avance de Doctrine 44/67 www.openclassrooms.com 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(); } Partie 2 : Utilisation avance de Doctrine 45/67 www.openclassrooms.com Comme vous le voyez, le template ajoute aussi une mthode hardDelete() auxobjets (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 Partie 2 : Utilisation avance de Doctrine 46/67 www.openclassrooms.com version actuelle est supprime, ou si elles doivent tre conserves. 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'indexest 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. Structure d'un template J'espre que vous voyez maintenant un peu mieuxce qu'est un template, et quoi il sert. V oyons un peu prsent comment tout cela est structur. V ous pourrez ainsi crer vos propres templates.Alors, lisez avec attention ce qui suit ! 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 mieuxce 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. Partie 2 : Utilisation avance de Doctrine 47/67 www.openclassrooms.com Hritage Doctrine_Template possde aussi deuxmthodes 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 ! Lien avec Doctrine_Record Si vous tes un tant soit peu curieux, vous aurez remarqu que Doctrine_Template et Doctrine_Record tendent tous les deux... Doctrine_Record_Abstract ! 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 auxobjets et auxtables. Ajout de mthodes aux objets Il existe un moyen trs simple d'ajouter des mthodes nos objets : je vous ai dit qu'il fallait considrer un objet et ses templates comme tant fusionns. Il suffit donc de dfinir une mthode dans un template, pour qu'elle soit accessible dans tous les objets l'implmentant ! Code : PHP <?php class Doctrine_Template_Timestampable extends Doctrine_Template { /* ... */ public function isOlderThan($date) { $date = new DateTime($date); $created = new DateTime($this->getInvoker()->{$this- >_options['created']['name']}); return $date->getTimestamp() > $created->getTimestamp(); } Partie 2 : Utilisation avance de Doctrine 48/67 www.openclassrooms.com } 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."; } Difficile de faire plus simple, non ? Ajout de mthodes aux tables V ous avez aussi la possibilit d'ajouter des mthodes auxclasses reprsentant les tables (ArticleTable, etc.). Il y a simplement une petite subtilit pour les diffrencier. V oyez plutt : Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { /* ... */ public function findArticlesOlderThanTableProxy($date) { $date = new DateTime($date); return $this->getTable() ->createQuery('a') ->where('a.'.$this->_options['created']['name'].' < ?', $date->getTimestamp()) ->execute(); } } 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"); Partie 2 : Utilisation avance de Doctrine 49/67 www.openclassrooms.com J'espre vous avoir mis l'eau la bouche avec ce chapitre, parce que maintenant, tout ceci va tre mis en pratique. Dans le prochain chapitre, vous crerez votre propre template. Un lien sera galement fait avec le chapitre prcdent, en lui attachant un listener. Partie 2 : Utilisation avance de Doctrine 50/67 www.openclassrooms.com TP : Cration d'un template Les deuxchapitres prcdents taient assez thoriques. Ici, vous allez mettre en pratique tout a ! Crer son propre template Maintenant que vous voyez un peu comment les templates fonctionnent, et quoi ils servent, vous allez certainement vouloir en crer de nouveauxadapts vos besoins. 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 mieuxle 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. Deuxd'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 Partie 2 : Utilisation avance de Doctrine 51/67 www.openclassrooms.com options classiques que vous trouverez dans les autres templates existants ). Les colonnes supplmentaires sont ajouter dans la mthode setTableDefinition(), et les relations l'intrieur de setUp(). Ajout d'un listener Je vous propose d'ajouter un listener au template. Il servira notamment pour la mise jour automatique de l'auteur de l'article. Pour rappel, il doit hriter de Doctrine_Record_Listener. Crez un rpertoire ~/lib/models/templates/listeners/. Nous placerons nos listeners ici. Je vous propose d'appeler notre classe Doctrine_Template_Listener_Publishable. Pour le construire, commencez par vous demander quelles mthodes seront utiles (c'est--dire quels vnements intercepter). 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, Partie 2 : Utilisation avance de Doctrine 52/67 www.openclassrooms.com // Par dfaut, l'article sera publiable. 'options' => array('notnull' => true, 'default' => 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 auxauteurs, 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 deuxchamps 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'] ); } } Partie 2 : Utilisation avance de Doctrine 53/67 www.openclassrooms.com /* ... */ } 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 deuxrelations 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; Partie 2 : Utilisation avance de Doctrine 54/67 www.openclassrooms.com $article->Author = $monObjetUser; 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. Partie 2 : Utilisation avance de Doctrine 55/67 www.openclassrooms.com S'il y en a parmi vous qui utilisent Symfony, vous pourrez le rcuprer avec quelque chose de similaire <?php sfContext::getInstance()->getUser()->getId();. Pr-insertion d'un article Nous allons ici travailler dans la mthode preInsert(), c'est--dire lors du premier enregistrement de l'article. Code : PHP - ~/lib/models/templates/listeners/Doctrine_Template_Listener_Publishable.php <?php class Doctrine_Template_Listener_Publishable extends Doctrine_Record_Listener { /* ... */ public function preInsert(Doctrine_Event $event) { // On rcupre ce dont on aura besoin. $invoker = $event->getInvoker(); $invokerTable = $invoker->getTable(); $modifiedFields = $invoker->getModified(); // Si cette colonne n'est pas dsactive : if(!$this->_options['created']['disabled']) { $createdField = $invokerTable->getFieldName($this- >_options['created']['name']); // On s'assure que le champ n'a pas t modifi manuellement. if(!isset($modifiedFields[$createdField])) { // On met le champ jour. $invoker->$createdField = $this->getCurrentUserId(); } } // Si cette colonne n'est pas dsactive : if(!$this->_options['updated']['disabled']) { $updatedField = $invokerTable->getFieldName($this- >_options['updated']['name']); // On s'assure que le champ n'a pas t modifi manuellement. if(!isset($modifiedFields[$updatedField])) { // On met le champ jour. $invoker->$updatedField = $this->getCurrentUserId(); } } } /* ... */ } 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. Partie 2 : Utilisation avance de Doctrine 56/67 www.openclassrooms.com 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. Mise jour d'un article Lors de la mise jour d'un article, nous modifions seulement la valeur de la colonne updated. Nous allons ici travailler sur deuxmthodes : preUpdate() lors d'une mise jour par l'objet directement, et preDqlUpdate() lors d'une mise jour par l'intermdiaire d'une requte DQL. Premier cas : avec Doctrine_Record Commenons par preUpdate(). C'est identique prcdemment. Code : PHP - ~/lib/models/templates/listeners/Doctrine_Template_Listener_Publishable.php <?php class Doctrine_Template_Listener_Publishable extends Doctrine_Record_Listener { /* ... */ public function preUpdate(Doctrine_Event $event) { // On rcupre ce dont on aura besoin. $invoker = $event->getInvoker(); $invokerTable = $invoker->getTable(); $modifiedFields = $invoker->getModified(); // Si cette colonne n'est pas dsactive : if(!$this->_options['updated']['disabled']) { $updatedField = $invokerTable->getFieldName($this- >_options['updated']['name']); // On s'assure que le champ n'a pas t modifi manuellement. if(!isset($modifiedFields[$updatedField])) { // On met le champ jour. $invoker->$updatedField = $this->getCurrentUserId(); } } } /* ... */ } Second cas : avec Doctrine_Query V oyons maintenant comment grer a lors d'une requte DQL : Code : PHP - ~/lib/models/templates/listeners/Doctrine_Template_Listener_Publishable.php <?php class Doctrine_Template_Listener_Publishable extends Doctrine_Record_Listener { /* ... */ public function preDqlUpdate(Doctrine_Event $event) Partie 2 : Utilisation avance de Doctrine 57/67 www.openclassrooms.com { // Si cette colonne n'est pas dsactive : if(!$this->_options['updated']['disabled']) { // On rcupre ce dont on aura besoin. $invokerTable = $event->getInvoker()->getTable(); $params = $event->getParams(); $updatedField = $invokerTable->getFieldName($this- >_options['updated']['name']); $q = $event->getQuery(); $field = $params['alias'].'.'.$updatedField; // Contient par exemple 'a.updated_by' // On s'assure que le champ n'a pas t modifi manuellement. if(!$q->contains($field)) { // On met le champ jour. $q->set($field, '?', $this->getCurrentUserId()); } } } /* ... */ } 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); Partie 2 : Utilisation avance de Doctrine 58/67 www.openclassrooms.com } } } /* ... */ } 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. Liaison des composants Il reste juste une chose rgler : l'utiliser dans le template ! En effet, nous n'avons pas encore indiqu au template qu'il doit l'utiliser. La mthode addListener() est l pour a. Mais vous le saviez dj, non ? Nous l'appellerons dans setTableDefinition() : Code : PHP - ~/lib/models/templates/Doctrine_Template_Publishable.php <?php class Doctrine_Template_Publishable extends Doctrine_Template { /* ... */ public function setTableDefinition() { /* ... */ $this->addListener(new Doctrine_Template_Listener_Publishable($this->_options)); } /* ... */ } 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', Partie 2 : Utilisation avance de Doctrine 59/67 www.openclassrooms.com 'class' => 'User', 'foreign' => 'id', 'type' => 'integer', 'disabled' => false, 'options' => array(), ), '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' => true), ), ); 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- Partie 2 : Utilisation avance de Doctrine 60/67 www.openclassrooms.com >_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'] )); } } } 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(); } } Partie 2 : Utilisation avance de Doctrine 61/67 www.openclassrooms.com } public function preUpdate(Doctrine_Event $event) { $invoker = $event->getInvoker(); $invokerTable = $invoker->getTable(); $modifiedFields = $invoker->getModified(); if(!$this->_options['updated']['disabled']) { $updatedField = $invokerTable->getFieldName($this- >_options['updated']['name']); if(!isset($modifiedFields[$updatedField])) { $invoker->$updatedField = $this- >getCurrentUserId(); } } } public function preDqlUpdate(Doctrine_Event $event) { if(!$this->_options['updated']['disabled']) { $invokerTable = $event->getInvoker()->getTable(); $params = $event->getParams(); $updatedField = $invokerTable->getFieldName($this- >_options['updated']['name']); $q = $event->getQuery(); $field = $params['alias'].'.'.$updatedField; if(!$q->contains($field)) { $q->set($field, '?', $this->getCurrentUserId()); } } } 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 ! Partie 2 : Utilisation avance de Doctrine 62/67 www.openclassrooms.com Partie 2 : Utilisation avance de Doctrine 63/67 www.openclassrooms.com 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. Dtail des principaux attributs ATTR_VALIDATE Je mettrai prochainement en ligne un chapitre consacr la validation des donnes. Nous parlerons plus en dtail de cet attribut ce moment-l. 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 : Partie 3 : Annexes 64/67 www.openclassrooms.com Code : PHP <?php $manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_LENGTHS | Doctrine_Core::VALIDATE_TYPES); 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 ceuxque 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. Partie 3 : Annexes 65/67 www.openclassrooms.com Ce tutoriel n'est pas termin !!! Partie 3 : Annexes 66/67 www.openclassrooms.com

PARTAGER SUR

Envoyer le lien par email
723
READS
3
DOWN
0
FOLLOW
4
EMBED

licence non indique


DOCUMENT # INDEX
Web Design 
img

Partagé par  Flanord

 Suivre

Auteur:
Source:Non communique

 

 

Flanord a également publié

*   268920-apprendre-a-utiliser-doctrine