Author: forresst Date: 2010-03-05 12:48:16 +0100 (Fri, 05 Mar 2010) New Revision: 28388
Modified: doc/branches/1.2/forms/fr/11-Doctrine-Integration.txt Log: [doc-fr][1.2] update doc in french, forms/11-Doctrine-Integration rev:en/23978 Modified: doc/branches/1.2/forms/fr/11-Doctrine-Integration.txt =================================================================== --- doc/branches/1.2/forms/fr/11-Doctrine-Integration.txt 2010-03-05 09:30:09 UTC (rev 28387) +++ doc/branches/1.2/forms/fr/11-Doctrine-Integration.txt 2010-03-05 11:48:16 UTC (rev 28388) @@ -1,9 +1,16 @@ Chapitre 11 - Intégration avec Doctrine -====================================== +======================================= -Dans un projet web, la plupart des formulaires sont utilisés pour créer ou modifier des objets du modèle. Ces objets sont généralement sérialisés dans une base de données grâce à un ORM. Le système de formulaires de Symfony offre une couche supplémentaire pour l'interfaçage avec Doctrine, l'ORM intégré à symfony, rendant l'implémentation de formulaires basés sur ces objets modèles plus aisée. +Dans un projet web, la plupart des formulaires sont utilisés pour créer ou modifier des +objets du modèle. Ces objets sont généralement sérialisés dans une base de données grâce +à un ORM. Le système de formulaires de Symfony offre une couche supplémentaire pour +l'interfaçage avec Doctrine, l'ORM intégré à symfony, rendant l'implémentation de formulaires +basés sur ces objets modèles plus aisée. -Ce chapitre détaille comment intégrer des formulaires avec les modèles objet Doctrine. Il est hautement recommandé d'être déjà familier avec Doctrine et son intégration dans symfony. Si ce n'est pas le cas, référez-vous au livre "[The symfony and Doctrine book](http://www.symfony-project.org/doctrine/1_2/)". +Ce chapitre détaille comment intégrer des formulaires avec les modèles objet Doctrine. +Il est hautement recommandé d'être déjà familier avec Doctrine et son intégration dans +symfony. Si ce n'est pas le cas, référez-vous au +livre "[The symfony and Doctrine book](http://www.symfony-project.org/doctrine/1_2/)". Avant de commencer : -------------------- @@ -166,9 +173,7 @@ >**SIDEBAR** >Personnalisation globale des formulaires Doctrine > ->En plus des classes générées pour chaque table, `doctrine:build-forms` génère également une classe `BaseFormDoctrine`. ->Cette classe vide est la classe de base de toutes les autres classes générées dans le répertoire `lib/form/base/`, elle permet de configurer globalement le comportement de chaque formulaire Doctrine. ->Par exemple, il est possible de changer facilement le formateur par défaut pour tous les formulaires Doctrine : +>En plus des classes générées pour chaque table, `doctrine:build-forms` génère également une classe `BaseFormDoctrine`. Cette classe vide est la classe de base de toutes les autres classes générées dans le répertoire `lib/form/base/`, elle permet de configurer globalement le comportement de chaque formulaire Doctrine. Par exemple, il est possible de changer facilement le formateur par défaut pour tous les formulaires Doctrine : > > [php] > abstract class BaseFormDoctrine extends sfFormDoctrine @@ -211,7 +216,9 @@ >**Note** >CRUD signifie Creation / Retrieval / Update / Deletion (Création / >Récupération / Mise à jour / Suppression) et résume les quatre opérations >basiques que l'on peut effectuer sur les données du modèle. -Dans le Listing 4-4, nous voyons que la tâche a généré cinq actions nous permettant de lister (`index`), créer (`create`), modifier (`edit`), enregistrer (`update`), et supprimer (`delete`) les objets de la classe `Author`. +Dans le Listing 4-4, nous voyons que la tâche a généré cinq actions nous permettant +de lister (`index`), créer (`create`), modifier (`edit`), enregistrer (`update`) et +supprimer (`delete`) les objets de la classe `Author`. Listing 4-4 - La classe `authorActions` générée par la tâche @@ -221,135 +228,124 @@ { public function executeIndex() { - $this->authorList = $this->getAuthorTable()->findAll(); + $this->author_list = Doctrine::getTable('Author') + ->createQuery('a') + ->execute(); } - public function executeCreate() + public function executeNew(sfWebRequest $request) { $this->form = new AuthorForm(); + } - $this->setTemplate('edit'); + public function executeCreate(sfWebRequest $request) + { + $this->forward404Unless($request->isMethod('post')); + + $this->form = new AuthorForm(); + + $this->processForm($request, $this->form); + + $this->setTemplate('new'); } - public function executeEdit($request) + public function executeEdit(sfWebRequest $request) { - $this->form = $this->getAuthorForm($request->getParameter('id')); + $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); + $this->form = new AuthorForm($author); } - public function executeUpdate($request) + public function executeUpdate(sfWebRequest $request) { - $this->forward404Unless($request->isMethod('post')); + $this->forward404Unless($request->isMethod('post') || $request->isMethod('put')); + $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); + $this->form = new AuthorForm($author); - $this->form = $this->getAuthorForm($request->getParameter('id')); + $this->processForm($request, $this->form); - $this->form->bind($request->getParameter('author')); - if ($this->form->isValid()) - { - $author = $this->form->save(); - - $this->redirect('author/edit?id='.$author->get('id')); - } - $this->setTemplate('edit'); } - public function executeDelete($request) + public function executeDelete(sfWebRequest $request) { - $this->forward404Unless($author = $this->getAuthorById($request->getParameter('id'))); + $request->checkCSRFProtection(); + $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); $author->delete(); $this->redirect('author/index'); } - private function getAuthorTable() + protected function processForm(sfWebRequest $request, sfForm $form) { - return Doctrine::getTable('Author'); - } + $form->bind($request->getParameter($form->getName())); + if ($form->isValid()) + { + $author = $form->save(); - private function getAuthorById($id) - { - return $this->getAuthorTable()->find($id); - } - - private function getAuthorForm($id) - { - $author = $this->getAuthorById($id); - - if ($author instanceof Author) - { - return new AuthorForm($author); + $this->redirect('author/edit?id='.$author->getId()); } - else - { - return new AuthorForm(); - } } } -Dans ce module, le cycle de vie du formulaire est géré par trois méthodes : `create`, `edit` et `update`. Il est aussi possible de demander à `doctrine:generate-crud` de générer une seule méthode couvrant les fonctionnalités des trois précédentes méthodes avec l'option `--non-atomic-actions` : +Dans ce module, le cycle de vie du formulaire est géré par trois méthodes : `create`, +`edit`, `update` et `processForm`. Il est aussi possible de faire cela d'une manière +moins longue en déplaçant ces 4 tâches dans une seule méthode, le listing 4-5 montre +un exemple simplifié de ceci. - $ ./symfony doctrine:generate-crud frontend author Author --non-atomic-actions +Listing 4-5 - Le cycle de vie du formulaire de la classe `authorActions` après quelques +refactorisation -Le code généré en utilisant `--non-atomic-actions` (Listing 4-5) est plus concis et moins verbeux. - -Listing 4-5 - La classe `authorActions` générée avec l'otion `--non-atomic-actions` - [php] - class authorActions extends sfActions + // In authorActions, replacing the create, edit, update and processForm methods + public function executeEdit($request) { - public function executeIndex() - { - $this->authorList = $this->getAuthorTable()->findAll(); - } + $this->form = new AuthorForm(Doctrine::getTable('Author')->find($request->getParameter('id'))); - public function executeEdit($request) + if ($request->isMethod('post')) { - $this->form = new AuthorForm(Doctrine::getTable('Author')->find($request->getParameter('id'))); - - if ($request->isMethod('post')) + $this->form->bind($request->getParameter('author')); + if ($this->form->isValid()) { - $this->form->bind($request->getParameter('author')); - if ($this->form->isValid()) - { - $author = $this->form->save(); - - $this->redirect('author/edit?id='.$author->getId()); - } + $author = $this->form->save(); + $this->redirect('author/edit?id='.$author->getId()); } } + } - public function executeDelete($request) - { - $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id'))); +>**NOTE** +>Les exemples qui suivent utilisent la valeur par défaut, le style plus verbeux donc vous aurez +>besoin de faire des ajustements en conséquence si vous souhaitez suivre l'approche dans le Listing +>4-5. Par exemple, dans votre modèle de formulaire, vous n'aurez besoin que de faire pointer le +>formulaire sur l'action edit indépendamment du fait que l'objet est nouveau ou ancien. - $author->delete(); +La tâche génère également trois templates et un partial, `indexSuccess`, +`editSuccess`, `newSuccess` et `_form`. Le template `_form` a été généré +sans la déclaration `<?php echo $form ?>`. Nous pouvons modifier ce comportement, +en utilisant `--non-verbose-templates` : - $this->redirect('author/index'); - } - } - -La tâche génère également deux templates, `indexSuccess` et `editSuccess`. Le template `editSuccess` a été généré sans la déclaration `<?php echo $form ?>`. Nous pouvons modifier ce comportement en utilisant `--non-verbose-templates` : - $ ./symfony doctrine:generate-crud frontend author Author --non-verbose-templates Cette option est utile lors de la phase de prototypage, comme le montre le Listing 4-6. -Listing 4-6 - Le template `editSuccess` +Listing 4-6 - Le template `_form` [php] - // apps/frontend/modules/author/templates/editSuccess.class.php - <?php $author = $form->getObject() ?> - <h1><?php echo $author->isNew() ? 'New' : 'Edit' ?> Author</h1> + // apps/frontend/modules/author/templates/_form.php + <?php include_stylesheets_for_form($form) ?> + <?php include_javascripts_for_form($form) ?> - <form action="<?php echo url_for('author/edit'.(!$author->isNew() ? '?id='.$author->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>> + <form action="<?php echo url_for('author/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>> + <?php if (!$form->getObject()->isNew()): ?> + <input type="hidden" name="sf_method" value="put" /> + <?php endif; ?> <table> <tfoot> <tr> <td colspan="2"> <a href="<?php echo url_for('author/index') ?>">Cancel</a> - <?php if (!$author->isNew()): ?> - <?php echo link_to('Delete', 'author/delete?id='.$author->getId(), array('post' => true, 'confirm' => 'Are you sure?')) ?> + <?php if (!$form->getObject()->isNew()): ?> + <?php echo link_to('Delete', 'author/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?> <?php endif; ?> <input type="submit" value="Save" /> </td> @@ -359,12 +355,18 @@ <?php echo $form ?> </tbody> </table> - </form> + </form> >**TIP** ->L'option `--with-show` nous permet de générer une action et un template utilisables pour voir un objet (en lecture seule). +>L'option `--with-show` nous permet de générer une action et un template utilisables pour +>voir un objet (en lecture seule). -Vous pouvez maintenant ouvrir l'URL `/frontend_dev.php/author` dans un navigateur pour voir le module généré (Figure 4-1 et Figure 4-2). Grâce au module généré, vous pouvez lister les auteurs, en ajouter de nouveaux, les éditer, les modifier et même les supprimer. Vous noterez également que les règles de validation fonctionnent. +Vous pouvez maintenant ouvrir l'URL `/frontend_dev.php/author` dans un navigateur pour voir le +module généré (Figure 4-1 et Figure 4-2). Prenez le temps de jouer avec l'interface. Grâce au +module généré, vous pouvez lister les auteurs, en ajouter un nouveau, éditer, modifier et +éventuellement supprimer. Vous remarquerez également que les règles de validation sont +opérationnelles. Notez que dans les figures suivantes, nous avons choisi de supprimer +le champ "actif". Figure 4-1 - Liste d'auteurs @@ -376,16 +378,14 @@ Nous pouvons maintenant répéter l'opération avec la classe `Article` : - $ ./symfony doctrine:generate-crud frontend article Article --non-verbose-templates --non-atomic-actions + $ ./symfony doctrine:generate-crud frontend article Article --non-verbose-templates -Le code généré est assez similaire à celui de la classe `Author`. Cependant, si vous essayez de créer un nouvel article, le code va lancer une erreur fatale comme vous pouvez le voir en Figure 4-3. +Le formulaire `ArticleForm` utilise le widget `sfWidgetFormDoctrineSelect` pour représenter +la relation entre l'objet `Article` et l'objet `Author`. Ce widget crée une liste déroulante +avec les auteurs. Durant l'affichage, les objets auteur sont convertis en chaîne de caractères +grâce à la méthode magique `__toString()`, qui doit être définie dans la classe `Author`, +comme montré en Listing 4-7. -Figure 4-3 - Les tables liées doivent définir la méthode `__toString()` - -` method") - -Le formulaire `ArticleForm` utilise le widget `sfWidgetFormDoctrineSelect` pour représenter la relation entre l'objet `Article` et l'objet `Author`. Ce widget crée une liste déroulante avec les auteurs. Durant l'affichage, les objets auteur sont convertis en chaîne de caractères grâce à la méthode magique `__toString()`, qui doit être définie dans la classe `Author`, comme montré en Listing 4-7. - Listing 4-7 - Implementation de `__toString()` pour la classe `Author` [php] @@ -397,20 +397,33 @@ } } -Tout comme pour la classe `Author`, vous pouvez créer la méthode `__toString()` pour les autres classes du modèle : `Article`, `Category`, et `Tag`. +Tout comme pour la classe `Author`, vous pouvez créer la méthode `__toString()` pour les autres +classes du modèle : `Article`, `Category`, et `Tag`. >**Note** ->sfDoctrineRecord va essayer de deviner la valeur de __toString() si vous ne la spécifiez pas vous-même. Elle regarde les colonnes nommées titre, nom, sujet, etc. pour les utiliser comme représentation de chaîne. +>sfDoctrineRecord va essayer de deviner la valeur de __toString() si vous ne la +>spécifiez pas vous-même. Elle regarde les colonnes nommées 'name', 'title', 'description', +>'subject', 'keywords' et enfin 'id' pour les utiliser comme représentation de chaîne. Si +>l'un de ces champs n'est pas trouvé, Doctrine retournera une chaîne d'alerte par défaut. +- + >**Tip** ->L'option `method` du widget `sfWidgetFormDoctrineSelect` change le nom de la méthode utilisée pour représenter un objet au format texte. +>L'option `method` du widget `sfWidgetFormDoctrineSelect` change la méthode +>utilisée pour représenter un objet au format texte. -La Figure 4-4 montre comment créer un article après avoir implémenté la méthode `__toString()`. +La Figure 4-4 montre comment créer un article après avoir implémenté la méthode +`__toString()`. Figure 4-4 - Création d'un article  +>**NOTE** +>Dans la figure 4-4, vous remarquerez que certains champs ne figurent pas sur le formulaire, +>par exemple `created_at` et `updated_at`. C'est parce que nous avons personnalisé la classe du +>formulaire. Vous allez apprendre à faire cela dans la section suivante. + Personnalisation des formulaires générés ---------------------------------------- @@ -431,8 +444,6 @@ { public function configure() { - // ... - $this->validatorSchema['slug']->setOption('required', false); $this->validatorSchema['content']->setOption('min_length', 5); @@ -462,9 +473,19 @@ } } -Le validateur `sfValidatorDoctrineUnique` est un `postValidator` s'exécutant sur toutes les données après les validations individuelles des champs. Afin de valider l'unicité de `slug`, le validateur doit être capable d'accéder non seulement à la valeur de `slug`, mais également à la valeur de(s) clé(s) primaire(s). Les règles de validation sont ainsi différentes entre la création et l'édition, puisque le slug peut rester le même lors de la mise à jour d'un article. +Le validateur `sfValidatorDoctrineUnique` est un `postValidator` s'exécutant sur toutes +les données après les validations individuelles des champs. Afin de valider l'unicité de +`slug`, le validateur doit être capable d'accéder non seulement à la valeur de `slug`, mais +également à la valeur de(s) clé(s) primaire(s). Les règles de validation sont ainsi +différentes entre la création et l'édition, puisque le slug peut rester le même lors +de la mise à jour d'un article. -Personnalisons maintenant le champ `active` de la table `author`, utilisé pour savoir si un auteur est actif. Le Listing 4-10 montre comment exclure les auteurs inactifs du formulaire `AuthorForm`, en modifiant l'option `query` du widget `DoctrineSelect` connecté au champ `author_id`. L'option `query` accepte un objet Doctrine Query, permettant de réduire les options disponibles dans la liste déroulante. +Personnalisons maintenant le champ `active` de la table `author`, utilisé pour savoir +si un auteur est actif. Le Listing 4-10 montre comment exclure les auteurs inactifs +du formulaire `ArticleForm`, en modifiant l'option `query` du widget `FormDoctrineSelect` +connecté au champ `author_id`. L'option `query` accepte un objet Doctrine Query, +permettant de réduire les options disponibles dans la +liste déroulante. Listing 4-10 - Personnalisation du widget `sfWidgetFormDoctrineSelect` @@ -502,7 +523,7 @@ } } -Dans le précédent exemple, nous avons défini un objet `Query` directement dans la méthode `configure()`. Dans notre projet, cette requête sera certainement utile en d'autres circonstances, il est donc préférable de créer une méthode `getActiveAuthorsQuery()` dans la classe `AuthorPeer` et d'appeler cette méthode depuis `ArticleForm` comme le montre le Listing 4-12. +Dans le précédent exemple, nous avons défini un objet `Query` directement dans la méthode `configure()`. Dans notre projet, cette requête sera certainement utile en d'autres circonstances, il est donc préférable de créer une méthode `getActiveAuthorsQuery()` dans la classe `AuthorTable` et d'appeler cette méthode depuis `ArticleForm` comme le montre le Listing 4-12. Listing 4-12 - Refactorisation de `Query` dans le modèle @@ -523,6 +544,8 @@ { public function configure() { + // ... + $authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery(); $this->widgetSchema['author_id']->setOption('query', $authorQuery); $this->validatorSchema['author_id']->setOption('query', $authorQuery); @@ -530,11 +553,19 @@ } >**TIP** ->Tout comme le widget `sfWidgetFormDoctrineSelect` et le validateur `sfValidatorDoctrineChoice` représentent une relation 1-n entre deux tables, le widget `sfWidgetDoctrineSelectMany` et le validateur `sfValidatorDoctrineChoiceMany` représentent une relation n-n et acceptent les mêmes options. Dans le formulaire `ArticleForm`, ces classes sont utilisées pour représenter une relation entre la table `article` et la table `tag`. +>Tout comme le widget `sfWidgetFormDoctrineSelect` et le +>validateur `sfValidatorDoctrineChoice` représentent une relation 1-n entre deux +>tables, le widget `sfWidgetDoctrineSelectMany` et le +>validateur `sfValidatorDoctrineChoiceMany` représentent une relation n-n et acceptent +>les mêmes options. Dans le formulaire `ArticleForm`, ces classes sont utilisées pour +>représenter une relation entre la table `article` et la table `tag`. ### Changer les validateurs -L'`email` étant défini comme un `string(255)` dans le schéma, symfony a créé un validateur `sfValidatorString()` restreignant la longueur maximale à 255 caractères. Ce champ est aussi supposé contenir des adresses email valides, le Listing 4-13 remplace le validateur généré par un `sfValidatorEmail`. +L'`email` étant défini comme un `string(255)` dans le schéma, symfony a créé +un validateur `sfValidatorString()` restreignant la longueur maximale à 255 +caractères. Ce champ est aussi supposé contenir des adresses email valides, le Listing +4-13 remplace le validateur généré par un validateur `sfValidatorEmail`. Listing 4-13 - Changement du validateur du champ `email` de la classe `AuthorForm` @@ -549,7 +580,10 @@ ### Ajouter un validateur -Nous avons vu dans la précédente partie comment modifier le validateur généré. Mais dans le cas du champ `email`, il serait utile de garder la validation de longueur maximale. Dans le Listing 4-14, nous utilisons le validateur `sfValidatorAnd` pour garantir la validité de l'email et vérifier la taille maximale possible pour le champ. +Nous avons vu dans la précédente partie comment modifier le validateur généré. Mais +dans le cas du champ `email`, il serait utile de garder la validation de longueur maximale. +Dans le Listing 4-14, nous utilisons le validateur `sfValidatorAnd` pour garantir la +validité de l'email et vérifier la taille maximale possible pour le champ. Listing 4-14 - Utilisation d'un validateur multiple @@ -565,7 +599,10 @@ } } -L'exemple précédent n'est pas parfait, car si nous décidons de modifier par la suite la longueur du champ `email` dans le schéma de base de données, nous devrons penser à le faire également dans le formulaire. Au lieu de remplacer le validateur généré, il est préférable d'en ajouter un, comme le montre le Listing 4-15. +L'exemple précédent n'est pas parfait, car si nous décidons de modifier par la suite la longueur +du champ `email` dans le schéma de base de données, nous devrons penser à le faire +également dans le formulaire. Au lieu de remplacer le validateur généré, il est +préférable d'en ajouter un, comme le montre le Listing 4-15. Listing 4-15 - Ajout d'un validateur @@ -583,12 +620,14 @@ ### Changer un widget -Dans le schéma de base de données, le champ `status` de la table `article` stocke les statuts d'articles dans une chaine de caractères. Les valeurs possibles ont été définies dans la classe `ArticlePeer`, comme montré dans le Listing 4-16. +Dans le schéma de base de données, le champ `status` de la table `article` stocke les +statuts d'articles dans une chaine de caractères. Les valeurs possibles ont été définies +dans la classe `ArticleTable`, comme montré dans le Listing 4-16. -Listing 4-16 - Définition des statuts possibles dans la classe `ArticlePeer` +Listing 4-16 - Définition des statuts possibles dans la classe `ArticleTable` [php] - class ArticlePeer extends BaseArticlePeer + class ArticleTable extends Doctrine_Table { static protected $statuses = array('draft', 'online', 'offline'); @@ -600,7 +639,9 @@ // ... } -Lors de l'édition d'un article, le champ `status` doit être représenté comme une liste déroulante au lieu d'un champ texte. Pour ce faire, changeons le widget utilisé, comme le montre le Listing 4-17. +Lors de l'édition d'un article, le champ `status` doit être représenté comme une liste déroulante +au lieu d'un champ texte. Pour ce faire, changeons le widget utilisé, comme +le montre le Listing 4-17. Listing 4-17 - Changement du widget pour le champ `status` @@ -609,11 +650,14 @@ { public function configure() { - $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); + // ... + + $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticleTable::getStatuses())); } } -Pour être exhaustifs, nous devons également changer le validateur pour être sûrs que le statut choisi est bien présent dans la liste des options possibles (Listing 4-18). +Pour être exhaustifs, nous devons également changer le validateur pour être sûrs que le statut +choisi est bien présent dans la liste des options possibles (Listing 4-18). Listing 4-18 - Modification du validateur du champ `status` @@ -622,17 +666,22 @@ { public function configure() { - $statuses = ArticlePeer::getStatuses(); + // ... - $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => $statuses)); - + $statuses = ArticleTable::getStatuses(); + + $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => $statuses)); $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys($statuses))); } } ### Supprimer un champ -La table `article` a tres colonnes spéciales, `created_at`, `updated_at` et `published_at`, dont la mise à jour est automatiquement gérée par Doctrine. Nous devons donc les supprimer du formulaire comme le montre le Listing 4-19, pour éviter que les utilisateurs les modifient. +La table `article` a trois colonnes spéciales, `created_at`, `updated_at` +et `published_at`. Les deux premiers sont automatiquement gérée par Doctrine comme +un comportement `timestampable`, le troisième que nous traiterons plus tard dans notre +propre code. Nous devons donc les supprimer du formulaire comme le montre le Listing 4-19, +pour éviter que les utilisateurs les modifient. Listing 4-19 - Suppression d'un champ @@ -641,6 +690,8 @@ { public function configure() { + // ... + unset($this->validatorSchema['created_at']); unset($this->widgetSchema['created_at']); @@ -652,7 +703,9 @@ } } -Afin de supprimer un champ, il est nécessaire de supprimer son validateur et son widget. Le Listing 4-20 montre comment il est possible de les supprimer tous les deux en une action, en utilisant le formulaire comme un tableau PHP. +Afin de supprimer un champ, il est nécessaire de supprimer son validateur et son +widget. Le Listing 4-20 montre comment il est possible de les supprimer tous les +deux en une action, en utilisant le formulaire comme un tableau PHP. Listing 4-20 - Suppression d'un champ en utilisant le formulaire comme un tableau PHP @@ -661,13 +714,16 @@ { public function configure() { + // ... + unset($this['created_at'], $this['updated_at'], $this['published_at']); } } ### Résumé -Pour résumer, le Listing 4-21 et le Listing 4-22 montrent les formulaires `ArticleForm` et `AuthorForm` personnalisés. +Pour résumer, le Listing 4-21 et le Listing 4-22 montrent les formulaires `ArticleForm` et +`AuthorForm` personnalisés. Listing 4-21 - Formulaire `ArticleForm` @@ -680,13 +736,13 @@ // widgets $this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40)); - $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); + $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticleTable::getStatuses())); $this->widgetSchema['author_id']->setOption('query', $authorQuery); // validators $this->validatorSchema['slug']->setOption('required', false); $this->validatorSchema['content']->setOption('min_length', 5); - $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticlePeer::getStatuses()))); + $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticleTable::getStatuses()))); $this->validatorSchema['author_id']->setOption('query', $authorQuery); unset($this['created_at'], $this['updated_at'], $this['published_at']); @@ -707,22 +763,42 @@ } } -Utiliser `doctrine:build-forms` permet de générer la plupart des éléments en introspectant le modèle objet. Cette automatisation est utile pour plusieurs raisons : +Utiliser la tâche `doctrine:build-forms` permet de générer la plupart +des éléments en introspectant le modèle objet. Cette automatisation +est utile pour plusieurs raisons : - * Cela rend la vie plus facile au développeur, lui épargnant un travail répétitif et redondant. Il peut alors se concentrer sur la personnalisation des validateurs et des widgets en fonction des règles métier spécifiques au projet. + * Cela rend la vie plus facile au développeur, lui épargnant un travail répétitif et + redondant. Il peut alors se concentrer sur la personnalisation des validateurs et des + widgets en fonction des règles métier spécifiques au projet. - * De plus, lorsque le schéma de base de données est mis à jour, les formulaires générés seront automatiquement mis à jour. Le développeur aura juste à adapter ses personnalisations. + * De plus, lorsque le schéma de base de données est mis à jour, les formulaires + générés seront automatiquement mis à jour. Le développeur aura juste à adapter + ses personnalisations. -La prochaine section décrira la personnalisation des actions et des templates générés par la tâche `doctrine:generate-crud`. +La prochaine section décrira la personnalisation des actions et des templates +générés par la tâche `doctrine:generate-crud`. Sérialisation des formulaires ----------------------------- -La précédente section nous montre comment personnaliser les formulaires générés par la tâche `doctrine:build-forms`. Dans la section courante, nous allons personnaliser le cycle de vie des formulaires, en commençant par le code généré par `doctrine:generate-crud`. +La précédente section nous montre comment personnaliser les formulaires générés par la tâche +`doctrine:build-forms`. Dans la section courante, nous allons personnaliser le cycle +de vie des formulaires, en commençant par le code généré par +`doctrine:generate-crud`. ### Valeurs par défaut -**Une instance de formulaire Doctrine est toujours reliée à un objet Doctrine**. L'objet Doctrine lié appartient toujours à la classe retournée par la méthode `getModelName()`. Par exemple, le formulaire `AuthorForm` peut seulement être lié à des objets appartenant à la classe `Author`. Ces objets sont soit des objets vides (une instance vide de la classe `Author`), soit les objets passés en premier argument au constructeur. Alors que le constructeur d'un formulaire « normal » prend un tableau de valeurs en premier argument, le constructeur d'un formulaire Doctrine prend un objet Doctrine. Cet objet est utilisé pour définir la valeur par défaut de chaque champ du fomulaire. La méthode `getObject()` retourne l'objet relié à l'instance courante, et la méthode `isNew()` permet de savoir si l'objet a été créé par le constructeur : +**Une instance de formulaire Doctrine est toujours reliée à un objet Doctrine**. +L'objet Doctrine lié appartient toujours à la classe retournée par la méthode +`getModelName()`. Par exemple, le formulaire `AuthorForm` peut seulement être +lié à des objets appartenant à la classe `Author`. Ces objets sont soit +des objets vides (une instance vide de la classe `Author`), soit les objets passés +en premier argument au constructeur. Alors que le constructeur d'un formulaire +« normal » prend un tableau de valeurs en premier argument, le constructeur +d'un formulaire Doctrine prend un objet Doctrine. Cet objet est utilisé pour définir +la valeur par défaut de chaque champ du fomulaire. La méthode `getObject()` retourne +l'objet relié à l'instance courante, et la méthode `isNew()` permet de savoir si +l'objet a été créé par le constructeur : [php] // création d'un nouvel objet @@ -740,35 +816,53 @@ ### Gestion du cycle de vie -Comme nous l'avons observé au début de ce chapitre, l'action `edit`, montrée dans le listing 4-23, gère le cycle de vie. +Comme nous l'avons observé au début de ce chapitre, les actions `new`, `edit` et +`create`, montrée dans le listing 4-23, gèrent le cycle de vie du formulaire. -Listing 4-23 - La méthode `executeEdit` du module `author` +Listing 4-23 - Les méthodes `executeNew`, `executeEdit`, `executeCreate` et +`processForm` du module `author` [php] // apps/frontend/modules/author/actions/actions.class.php class authorActions extends sfActions { // ... + public function executeNew(sfWebRequest $request) + { + $this->form = new AuthorForm(); + } - public function executeEdit($request) + public function executeCreate(sfWebRequest $request) { - $author = Doctrine::getTable('Author')->find($request->getParameter('id')); + $this->forward404Unless($request->isMethod('post')); + + $this->form = new AuthorForm(); + + $this->processForm($request, $this->form); + + $this->setTemplate('new'); + } + + public function executeEdit(sfWebRequest $request) + { + $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); $this->form = new AuthorForm($author); - - if ($request->isMethod('post')) + } + + protected function processForm(sfWebRequest $request, sfForm $form) + { + $form->bind($request->getParameter($form->getName())); + if ($form->isValid()) { - $this->form->bind($request->getParameter('author')); - if ($this->form->isValid()) - { - $author = $this->form->save(); + $author = $form->save(); - $this->redirect('author/edit?id='.$author->getId()); - } + $this->redirect('author/edit?id='.$author->getId()); } } } -Même si l'action `edit` ressemble aux actions que nous avons pu décrire dans les chapitres précédents, nous pouvons remarquer quelques différences : +Même si l'action `edit` ressemble aux actions que nous avons pu décrire dans +les chapitres précédents, nous pouvons remarquer quelques différences : * Un objet Doctrine de la classe `Author` est passé en premier paramètre du constructeur du formulaire : @@ -776,39 +870,45 @@ $author = Doctrine::getTable('Author')->find($request->getParameter('id')); $this->form = new AuthorForm($author); - * Le format des attributs `name` des widgets est automatiquement défini de manière à récupérer les données soumises dans un tableau PHP nommé d'après la table liée (`author`) : + * Le format des attributs `name` des widgets est automatiquement défini de manière à + récupérer les données soumises dans un tableau PHP nommé d'après la table liée (`author`) : [php] - $this->form->bind($request->getParameter('author')); + $form->bind($request->getParameter($form->getName())); - * Lorsque le formulaire est valide, un appel à la méthode `save()` crée ou met à jour les objets Doctrine reliés au formulaire : + * Lorsque le formulaire est valide, un appel à la méthode `save()` crée ou met à jour + les objets Doctrine reliés au formulaire : [php] - $author = $this->form->save(); + $author = $form->save(); ### Créer et modifier un objet Doctrine -Le code du Listing 4-23 gère en une seule méthode la création et la modification d'objets de la classe `Author` : +Le code du Listing 4-23 gère en une seule méthode la création et la modification d'objets de la +classe `Author` : * Création d'un nouvel objet `Author` : - * L'action `index` est appelée sans paramètre `id` (`$request->getParameter('id')` vaut `null`) + * L'action `create` est appelée - * L'appel à la méthode `find()` renvoie dont `null` - * L'objet `form` est alors lié à un objet Doctrine `Author` vide - * L'appel à `$this->form->save()` crée un nouvel objet `Author` en conséquence quand un formulaire valide est soumis + * L'appel à `$form->save()` crée un nouvel objet `Author` en conséquence + quand un formulaire valide est soumis * Modification d'un objet Author` existant : - * L'action `index` est appelée avec un paramètre `id` (`$request->getParameter('id')` représentant la clé primaire de l'objet `Author` à modifier) + * L'action `update` est appelée avec un paramètre `id` + (`$request->getParameter('id')` représentant la clé primaire de + l'objet `Author` à modifier) - * L'appel à la méthode `find()` retourne l'objet `Author` associé à la clé primaire + * L'appel à la méthode `find()` retourne l'objet `Author` associé à + la clé primaire * L'objet `form` est donc lié à l'objet précédemment trouvé - * L'appel à `$this->form->save()` met à jour l'objet `Author` quand un formulaire valide est soumis + * L'appel à `$form->save()` met à jour l'objet `Author` quand un formulaire valide + est soumis ### La méthode `save()` @@ -842,25 +942,30 @@ ### Gestion des envois de fichiers -La méthode `save()` met automatiquement à jour les objets Doctrine, mais ne peut pas gérer les autres éléments tels que les envois de fichiers. +La méthode `save()` met automatiquement à jour les objets Doctrine, mais ne peut pas +gérer les autres éléments tels que les envois de fichiers. -Voyons comment attacher un fichier à chaque article. Les fichiers sont enregistrés dans le répertoire `web/uploads` et une référence vers le fichier est gardée dans le champ `file` de la table `article`, comme le montre le Listing 4-24. +Voyons comment attacher un fichier à chaque article. Les fichiers sont enregistrés dans +le répertoire `web/uploads` et une référence vers le fichier est gardée dans le champ +`file` de la table `article`, comme le montre le Listing 4-24. Listing 4-24 - Schéma pour la table `article` avec le fichier associé [yml] - // config/schema.yml - doctrine: - article: - // ... - file: string(255) + // config/doctrine/schema.yml + Article: + // ... + file: string(255) Après chaque mise à jour du schéma, vous devez mettre à jour le modèle objet, la base de données et les formulaires associés : $ ./symfony doctrine:build-all ->**Attention** ->Soyez conscients que la tâche `doctrine:build-all` supprime toutes les tables du schéma et les recrée. Les données dans les tables sont donc réécrites. C'est pourquoi il est important de créer des données de test (`fixtures`) qui pourront être rechargées à chaque modification du modèle. +>**Caution** +>Soyez conscients que la tâche `doctrine:build-all` supprime toutes les tables du schéma +>et les recrée. Les données dans les tables sont donc réécrites. C'est pourquoi il est +>important de créer des données de test (`fixtures`) qui pourront être rechargées à +>chaque modification du modèle. Le Listing 4-25 montre comment modifier la classe `ArticleForm` afin de relier un widget et un validateur au champ `file`. @@ -878,8 +983,19 @@ } } -Comme pour chaque formulaire permettant l'envoi d'un fichier, n'oubliez pas d'ajouter l'attribut `enctype` à la balise `form` dans le template (voyez le Chapitre 2 pour plus d'informations concernant la gestion d'envois de fichiers). +Comme pour chaque formulaire permettant l'envoi d'un fichier, n'oubliez pas d'ajouter +l'attribut `enctype` à la balise `form` dans le template (voyez le Chapitre 2 pour +plus d'informations concernant la gestion d'envois de fichiers). +>**TIP** +>Lorsque vous créez votre modèle de formulaire, vous pouvez vérifier si le formulaire +>contient des champs de fichier, et ajouter l'attribut `enctype` automatiquement : +> +> [PHP] +> <?php if ($form->isMultipart() echo 'enctype="multipart/form-data" '; ?> +> +>Ce code est automatiquement ajoutée lorsque votre formulaire est créé par la tâche generate-crud. + Le Listing 4-26 montre les modifications à appliquer lors de l'enregistrement du formulaire pour enregistrer le fichier sur le serveur et stocker son chemin dans l'objet `article`. Listing 4-26 - Enregistrement de l'objet `article` et du fichier envoyé dans une action @@ -944,9 +1060,15 @@ > >Les actions générées par la tâche `doctrine:generate-crud` ne devraient >généralement pas être modifiées. > ->La logique que vous pourriez ajouter dans l'action `edit`, particulièrement durant la sérialisation du formulaire, doit souvent être déplacée dans les classes du modèle ou dans les classes du formulaire. +>La logique que vous pourriez ajouter dans l'action `edit`, particulièrement durant la sérialisation du +>formulaire, doit souvent être déplacée dans les classes du modèle ou dans les classes du formulaire. > ->Nous avons juste vu un exemple de refactorisation dans la classe formulaire afin d'effectuer un stockage de fichier. Prenons un autre exemple lié au modèle. Le formulaire `ArticleForm` a un champ `slug`. Nous avons vu que ce champ devrait être calculé automatiquement à partir du champ `title`, et qu'il pourrait éventuellement être spécifié par l'utilisateur. Cette logique ne dépend pas du formulaire. Elle appartient donc au modèle, comme le montre le code suivant : +>Nous avons juste vu un exemple de refactorisation dans la classe formulaire afin +>d'effectuer un stockage de fichier. Prenons un autre exemple lié au modèle. Le +>formulaire `ArticleForm` a un champ `slug`. Nous avons vu que ce champ devrait +>être calculé automatiquement à partir du champ `title`, et qu'il pourrait +>éventuellement être spécifié par l'utilisateur. Cette logique ne dépend pas du +>formulaire. Elle appartient donc au modèle, comme le montre le code suivant : > > [php] > class Article extends BaseArticle @@ -967,13 +1089,14 @@ > } > } > ->Le but principal de ces refactorisations est de respecter la séparation dans les couches applicatives, particulièrement la réutilisation du code. +>Le but principal de ces refactorisations est de respecter la séparation dans les couches applicatives, +>particulièrement la réutilisation du code. ### Personnalisation de la méthode `doSave()` Nous avons vu que l'enregistrement d'un objet était fait dans une transaction afin de garantir que chaque opération liée à l'enregistrement soit exécutée correctement. Lorsque la méthode `save()` est surchargée comme nous l'avons fait dans la section précédente pour enregistrer le fichier envoyé, le code exécuté est indépendant de cette transaction. -Le Listing 4-28 nous montre comment utiliser la méthode `doSave()` pour insérer dans la transaction globale le code enregistrant le fichier envoyé. +Le Listing 4-28 nous montre comment utiliser la méthode `doSave()` pour insérer dans la transaction globale le code enregistrant le fichier envoyé. Listing 4-28 - Surcharge de la méthode `doSave()` dans le formulaire `ArticleForm` @@ -1014,9 +1137,9 @@ { // ... - public function updateObject() + public function updateObject($values = null) { - $object = parent::updateObject(); + $object = parent::updateObject($values); $object->setFile(str_replace(sfConfig::get('sf_upload_dir').'/', '', $object->getFile())); @@ -1024,4 +1147,4 @@ } } -La méthode `updateObject()` est appelée par la méthode `doSave()` avant d'enregistrer l'objet dans la base de données. \ No newline at end of file +La méthode `updateObject()` est appelée par la méthode `doSave()` avant d'enregistrer l'objet dans la base de données. -- You received this message because you are subscribed to the Google Groups "symfony SVN" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/symfony-svn?hl=en.
