Development

Changeset 28388

You must first sign up to be able to contribute.

Changeset 28388

Show
Ignore:
Timestamp:
03/05/10 12:48:16 (5 years ago)
Author:
forresst
Message:

[doc-fr][1.2] update doc in french, forms/11-Doctrine-Integration rev:en/23978

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • doc/branches/1.2/forms/fr/11-Doctrine-Integration.txt

    r23978 r28388  
    11Chapitre 11 - Intégration avec Doctrine 
    2 ====================================== 
    3  
    4 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. 
    5  
    6 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/)". 
     2======================================= 
     3 
     4Dans un projet web, la plupart des formulaires sont utilisés pour créer ou modifier des 
     5objets du modèle. Ces objets sont généralement sérialisés dans une base de données grâce 
     6à un ORM. Le système de formulaires de Symfony offre une couche supplémentaire pour 
     7l'interfaçage avec Doctrine, l'ORM intégré à symfony, rendant l'implémentation de formulaires 
     8basés sur ces objets modèles plus aisée. 
     9 
     10Ce chapitre détaille comment intégrer des formulaires avec les modèles objet Doctrine. 
     11Il est hautement recommandé d'être déjà familier avec Doctrine et son intégration dans 
     12symfony. Si ce n'est pas le cas, référez-vous au 
     13livre "[The symfony and Doctrine book](http://www.symfony-project.org/doctrine/1_2/)". 
    714 
    815Avant de commencer : 
     
    167174>Personnalisation globale des formulaires Doctrine 
    168175> 
    169 >En plus des classes générées pour chaque table, `doctrine:build-forms` génère également une classe `BaseFormDoctrine`.  
    170 >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. 
    171 >Par exemple, il est possible de changer facilement le formateur par défaut pour tous les formulaires Doctrine : 
     176>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 : 
    172177> 
    173178>     [php] 
     
    212217>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. 
    213218 
    214 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`. 
     219Dans le Listing 4-4, nous voyons que la tâche a généré cinq actions nous permettant 
     220de lister (`index`), créer (`create`), modifier (`edit`), enregistrer (`update`) et 
     221supprimer (`delete`) les objets de la classe `Author`. 
    215222 
    216223Listing 4-4 - La classe `authorActions` générée par la tâche 
     
    222229      public function executeIndex() 
    223230      { 
    224         $this->authorList = $this->getAuthorTable()->findAll(); 
    225       } 
    226  
    227       public function executeCreate() 
     231        $this->author_list = Doctrine::getTable('Author') 
     232          ->createQuery('a') 
     233          ->execute(); 
     234      } 
     235 
     236      public function executeNew(sfWebRequest $request) 
    228237      { 
    229238        $this->form = new AuthorForm(); 
     239      } 
     240 
     241      public function executeCreate(sfWebRequest $request) 
     242      { 
     243        $this->forward404Unless($request->isMethod('post')); 
     244 
     245        $this->form = new AuthorForm(); 
     246 
     247        $this->processForm($request, $this->form); 
     248 
     249        $this->setTemplate('new'); 
     250      } 
     251 
     252      public function executeEdit(sfWebRequest $request) 
     253      { 
     254        $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); 
     255        $this->form = new AuthorForm($author); 
     256      } 
     257 
     258      public function executeUpdate(sfWebRequest $request) 
     259      { 
     260        $this->forward404Unless($request->isMethod('post') || $request->isMethod('put')); 
     261        $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); 
     262        $this->form = new AuthorForm($author); 
     263 
     264        $this->processForm($request, $this->form); 
    230265 
    231266        $this->setTemplate('edit'); 
    232267      } 
    233268 
    234       public function executeEdit($request) 
    235       { 
    236         $this->form = $this->getAuthorForm($request->getParameter('id')); 
    237       } 
    238  
    239       public function executeUpdate($request) 
    240       { 
    241         $this->forward404Unless($request->isMethod('post')); 
    242  
    243         $this->form = $this->getAuthorForm($request->getParameter('id')); 
    244  
     269      public function executeDelete(sfWebRequest $request) 
     270      { 
     271        $request->checkCSRFProtection(); 
     272 
     273        $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); 
     274        $author->delete(); 
     275 
     276        $this->redirect('author/index'); 
     277      } 
     278 
     279      protected function processForm(sfWebRequest $request, sfForm $form) 
     280      { 
     281        $form->bind($request->getParameter($form->getName())); 
     282        if ($form->isValid()) 
     283        { 
     284          $author = $form->save(); 
     285 
     286          $this->redirect('author/edit?id='.$author->getId()); 
     287        } 
     288      } 
     289    } 
     290 
     291Dans ce module, le cycle de vie du formulaire est géré par trois méthodes : `create`, 
     292`edit`, `update` et `processForm`. Il est aussi possible de faire cela d'une manière 
     293moins longue en déplaçant ces 4 tâches dans une seule méthode, le listing 4-5 montre 
     294un exemple simplifié de ceci. 
     295 
     296Listing 4-5 - Le cycle de vie du formulaire de la classe `authorActions` après quelques 
     297refactorisation 
     298 
     299    [php] 
     300    // In authorActions, replacing the create, edit, update and processForm methods 
     301    public function executeEdit($request) 
     302    { 
     303      $this->form = new AuthorForm(Doctrine::getTable('Author')->find($request->getParameter('id'))); 
     304 
     305      if ($request->isMethod('post')) 
     306      { 
    245307        $this->form->bind($request->getParameter('author')); 
    246308        if ($this->form->isValid()) 
    247309        { 
    248310          $author = $this->form->save(); 
    249  
    250           $this->redirect('author/edit?id='.$author->get('id')); 
     311          $this->redirect('author/edit?id='.$author->getId()); 
    251312        } 
    252  
    253         $this->setTemplate('edit'); 
    254       } 
    255  
    256       public function executeDelete($request) 
    257       { 
    258         $this->forward404Unless($author = $this->getAuthorById($request->getParameter('id'))); 
    259  
    260         $author->delete(); 
    261  
    262         $this->redirect('author/index'); 
    263       } 
    264  
    265       private function getAuthorTable() 
    266       { 
    267         return Doctrine::getTable('Author'); 
    268       } 
    269  
    270       private function getAuthorById($id) 
    271       { 
    272         return $this->getAuthorTable()->find($id); 
    273       } 
    274  
    275       private function getAuthorForm($id) 
    276       { 
    277         $author = $this->getAuthorById($id); 
    278  
    279         if ($author instanceof Author) 
    280         { 
    281           return new AuthorForm($author); 
    282         } 
    283         else 
    284         { 
    285           return new AuthorForm(); 
    286         } 
    287       } 
    288     } 
    289  
    290 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` : 
    291  
    292     $ ./symfony doctrine:generate-crud frontend author Author --non-atomic-actions 
    293  
    294 Le code généré en utilisant `--non-atomic-actions` (Listing 4-5) est plus concis et moins verbeux. 
    295  
    296 Listing 4-5 - La classe `authorActions` générée avec l'otion `--non-atomic-actions` 
    297  
    298     [php] 
    299     class authorActions extends sfActions 
    300     { 
    301       public function executeIndex() 
    302       { 
    303         $this->authorList = $this->getAuthorTable()->findAll(); 
    304       } 
    305  
    306       public function executeEdit($request) 
    307       { 
    308         $this->form = new AuthorForm(Doctrine::getTable('Author')->find($request->getParameter('id'))); 
    309  
    310         if ($request->isMethod('post')) 
    311         { 
    312           $this->form->bind($request->getParameter('author')); 
    313           if ($this->form->isValid()) 
    314           { 
    315             $author = $this->form->save(); 
    316  
    317             $this->redirect('author/edit?id='.$author->getId()); 
    318           } 
    319         } 
    320       } 
    321  
    322       public function executeDelete($request) 
    323       { 
    324         $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id'))); 
    325  
    326         $author->delete(); 
    327  
    328         $this->redirect('author/index'); 
    329       } 
    330     } 
    331  
    332 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` : 
     313      } 
     314    } 
     315 
     316>**NOTE** 
     317>Les exemples qui suivent utilisent la valeur par défaut, le style plus verbeux donc vous aurez 
     318>besoin de faire des ajustements en conséquence si vous souhaitez suivre l'approche dans le Listing 
     319>4-5. Par exemple, dans votre modèle de formulaire, vous n'aurez besoin que de faire pointer le 
     320>formulaire sur l'action edit indépendamment du fait que l'objet est nouveau ou ancien. 
     321 
     322La tâche génère également trois templates et un partial, `indexSuccess`, 
     323`editSuccess`, `newSuccess` et `_form`. Le template `_form` a été généré 
     324sans la déclaration `<?php echo $form ?>`. Nous pouvons modifier ce comportement, 
     325en utilisant `--non-verbose-templates` : 
    333326 
    334327    $ ./symfony doctrine:generate-crud frontend author Author --non-verbose-templates 
     
    336329Cette option est utile lors de la phase de prototypage, comme le montre le Listing 4-6. 
    337330 
    338 Listing 4-6 - Le template `editSuccess` 
    339  
    340     [php] 
    341     // apps/frontend/modules/author/templates/editSuccess.class.php 
    342     <?php $author = $form->getObject() ?> 
    343     <h1><?php echo $author->isNew() ? 'New' : 'Edit' ?> Author</h1> 
    344  
    345     <form action="<?php echo url_for('author/edit'.(!$author->isNew() ? '?id='.$author->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>> 
     331Listing 4-6 - Le template `_form` 
     332 
     333    [php] 
     334    // apps/frontend/modules/author/templates/_form.php 
     335    <?php include_stylesheets_for_form($form) ?> 
     336    <?php include_javascripts_for_form($form) ?> 
     337 
     338    <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" ' ?>> 
     339    <?php if (!$form->getObject()->isNew()): ?> 
     340    <input type="hidden" name="sf_method" value="put" /> 
     341    <?php endif; ?> 
    346342      <table> 
    347343        <tfoot> 
     
    349345            <td colspan="2"> 
    350346              &nbsp;<a href="<?php echo url_for('author/index') ?>">Cancel</a> 
    351               <?php if (!$author->isNew()): ?> 
    352                 &nbsp;<?php echo link_to('Delete', 'author/delete?id='.$author->getId(), array('post' => true, 'confirm' => 'Are you sure?')) ?> 
     347              <?php if (!$form->getObject()->isNew()): ?> 
     348                &nbsp;<?php echo link_to('Delete', 'author/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?> 
    353349              <?php endif; ?> 
    354350              <input type="submit" value="Save" /> 
     
    360356        </tbody> 
    361357      </table> 
    362     </form> 
     358      </form> 
    363359 
    364360>**TIP** 
    365 >L'option `--with-show` nous permet de générer une action et un template utilisables pour voir un objet (en lecture seule).  
    366  
    367 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. 
     361>L'option `--with-show` nous permet de générer une action et un template utilisables pour 
     362>voir un objet (en lecture seule).  
     363 
     364Vous pouvez maintenant ouvrir l'URL `/frontend_dev.php/author` dans un navigateur pour voir le 
     365module généré (Figure 4-1 et Figure 4-2). Prenez le temps de jouer avec l'interface. Grâce au 
     366module généré, vous pouvez lister les auteurs, en ajouter un nouveau, éditer, modifier et 
     367éventuellement supprimer. Vous remarquerez également que les règles de validation sont 
     368opérationnelles. Notez que dans les figures suivantes, nous avons choisi de supprimer 
     369le champ "actif". 
    368370 
    369371Figure 4-1 - Liste d'auteurs 
     
    377379Nous pouvons maintenant répéter l'opération avec la classe `Article` : 
    378380 
    379     $ ./symfony doctrine:generate-crud frontend article Article --non-verbose-templates --non-atomic-actions 
    380  
    381 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. 
    382  
    383 Figure 4-3 - Les tables liées doivent définir la méthode `__toString()` 
    384  
    385 ![Linked Tables must define the `__toString()` method](/images/forms_book/en/04_03.png "Linked Tables must define the `__toString()` method") 
    386  
    387 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. 
     381    $ ./symfony doctrine:generate-crud frontend article Article --non-verbose-templates 
     382 
     383Le formulaire `ArticleForm` utilise le widget `sfWidgetFormDoctrineSelect` pour représenter 
     384la relation entre l'objet `Article` et l'objet `Author`. Ce widget crée une liste déroulante 
     385avec les auteurs. Durant l'affichage, les objets auteur sont convertis en chaîne de caractères 
     386grâce à la méthode magique `__toString()`, qui doit être définie dans la classe `Author`, 
     387comme montré en Listing 4-7. 
    388388 
    389389Listing 4-7 - Implementation de `__toString()` pour la classe `Author` 
     
    398398    } 
    399399 
    400 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`. 
     400Tout comme pour la classe `Author`, vous pouvez créer la méthode `__toString()` pour les autres 
     401classes du modèle : `Article`, `Category`, et `Tag`. 
    401402 
    402403>**Note** 
    403 >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. 
     404>sfDoctrineRecord va essayer de deviner la valeur de __toString() si vous ne la 
     405>spécifiez pas vous-même. Elle regarde les colonnes nommées 'name', 'title', 'description', 
     406>'subject', 'keywords' et enfin 'id' pour les utiliser comme représentation de chaîne. Si 
     407>l'un de ces champs n'est pas trouvé, Doctrine retournera une chaîne d'alerte par défaut. 
     408 
     409
    404410 
    405411>**Tip** 
    406 >L'option `method` du widget `sfWidgetFormDoctrineSelect` change le nom de la méthode utilisée pour représenter un objet au format texte. 
    407  
    408 La Figure 4-4 montre comment créer un article après avoir implémenté la méthode `__toString()`. 
     412>L'option `method` du widget `sfWidgetFormDoctrineSelect` change la méthode 
     413>utilisée pour représenter un objet au format texte. 
     414 
     415La Figure 4-4 montre comment créer un article après avoir implémenté la méthode 
     416`__toString()`. 
    409417 
    410418Figure 4-4 - Création d'un article 
    411419 
    412420![Creating an Article](/images/forms_book/en/04_04.png "Creating an Article") 
     421 
     422>**NOTE** 
     423>Dans la figure 4-4, vous remarquerez que certains champs ne figurent pas sur le formulaire, 
     424>par exemple `created_at` et `updated_at`. C'est parce que nous avons personnalisé la classe du 
     425>formulaire. Vous allez apprendre à faire cela dans la section suivante. 
    413426 
    414427Personnalisation des formulaires générés 
     
    432445      public function configure() 
    433446      { 
    434         // ... 
    435  
    436447        $this->validatorSchema['slug']->setOption('required', false); 
    437448        $this->validatorSchema['content']->setOption('min_length', 5); 
     
    463474    } 
    464475 
    465 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. 
    466  
    467 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. 
     476Le validateur `sfValidatorDoctrineUnique` est un `postValidator` s'exécutant sur toutes 
     477les données après les validations individuelles des champs. Afin de valider l'unicité de 
     478`slug`, le validateur doit être capable d'accéder non seulement à la valeur de `slug`, mais 
     479également à la valeur de(s) clé(s) primaire(s). Les règles de validation sont ainsi 
     480différentes entre la création et l'édition, puisque le slug peut rester le même lors 
     481de la mise à jour d'un article. 
     482 
     483Personnalisons maintenant le champ `active` de la table `author`, utilisé pour savoir 
     484si un auteur est actif. Le Listing 4-10 montre comment exclure les auteurs inactifs 
     485du formulaire `ArticleForm`, en modifiant l'option `query` du widget `FormDoctrineSelect` 
     486connecté au champ `author_id`. L'option `query` accepte un objet Doctrine Query, 
     487permettant de réduire les options disponibles dans la 
     488liste déroulante. 
    468489 
    469490Listing 4-10 - Personnalisation du widget `sfWidgetFormDoctrineSelect` 
     
    503524    } 
    504525 
    505 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. 
     526Dans 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. 
    506527 
    507528Listing 4-12 - Refactorisation de `Query` dans le modèle 
     
    524545      public function configure() 
    525546      { 
     547        // ... 
     548       
    526549        $authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery(); 
    527550        $this->widgetSchema['author_id']->setOption('query', $authorQuery); 
     
    531554 
    532555>**TIP** 
    533 >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`. 
     556>Tout comme le widget `sfWidgetFormDoctrineSelect` et le 
     557>validateur `sfValidatorDoctrineChoice` représentent une relation 1-n entre deux 
     558>tables, le widget `sfWidgetDoctrineSelectMany` et le 
     559>validateur `sfValidatorDoctrineChoiceMany` représentent une relation n-n et acceptent 
     560>les mêmes options. Dans le formulaire `ArticleForm`, ces classes sont utilisées pour 
     561>représenter une relation entre la table `article` et la table `tag`. 
    534562 
    535563### Changer les validateurs 
    536564 
    537 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`. 
     565L'`email` étant défini comme un `string(255)` dans le schéma, symfony a créé 
     566un validateur `sfValidatorString()` restreignant la longueur maximale à 255 
     567caractères. Ce champ est aussi supposé contenir des adresses email valides, le Listing 
     5684-13 remplace le validateur généré par un validateur `sfValidatorEmail`. 
    538569 
    539570Listing 4-13 - Changement du validateur du champ `email` de la classe `AuthorForm` 
     
    550581### Ajouter un validateur 
    551582 
    552 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. 
     583Nous avons vu dans la précédente partie comment modifier le validateur généré. Mais 
     584dans le cas du champ `email`, il serait utile de garder la validation de longueur maximale. 
     585Dans le Listing 4-14, nous utilisons le validateur `sfValidatorAnd` pour garantir la 
     586validité de l'email et vérifier la taille maximale possible pour le champ. 
    553587 
    554588Listing 4-14 - Utilisation d'un validateur multiple 
     
    566600    } 
    567601 
    568 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. 
     602L'exemple précédent n'est pas parfait, car si nous décidons de modifier par la suite la longueur 
     603du champ `email` dans le schéma de base de données, nous devrons penser à le faire 
     604également dans le formulaire. Au lieu de remplacer le validateur généré, il est 
     605préférable d'en ajouter un, comme le montre le Listing 4-15. 
    569606 
    570607Listing 4-15 - Ajout d'un validateur 
     
    584621### Changer un widget 
    585622 
    586 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. 
    587  
    588 Listing 4-16 - Définition des statuts possibles dans la classe `ArticlePeer` 
    589  
    590     [php] 
    591     class ArticlePeer extends BaseArticlePeer 
     623Dans le schéma de base de données, le champ `status` de la table `article` stocke les 
     624statuts d'articles dans une chaine de caractères. Les valeurs possibles ont été définies 
     625dans la classe `ArticleTable`, comme montré dans le Listing 4-16. 
     626 
     627Listing 4-16 - Définition des statuts possibles dans la classe `ArticleTable` 
     628 
     629    [php] 
     630    class ArticleTable extends Doctrine_Table 
    592631    { 
    593632      static protected $statuses = array('draft', 'online', 'offline'); 
     
    601640    } 
    602641 
    603 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. 
     642Lors de l'édition d'un article, le champ `status` doit être représenté comme une liste déroulante 
     643au lieu d'un champ texte. Pour ce faire, changeons le widget utilisé, comme 
     644le montre le Listing 4-17. 
    604645 
    605646Listing 4-17 - Changement du widget pour le champ `status` 
     
    610651      public function configure() 
    611652      { 
    612         $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); 
    613       } 
    614     } 
    615  
    616 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). 
     653        // ... 
     654         
     655        $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticleTable::getStatuses())); 
     656      } 
     657    } 
     658 
     659Pour être exhaustifs, nous devons également changer le validateur pour être sûrs que le statut 
     660choisi est bien présent dans la liste des options possibles (Listing 4-18). 
    617661 
    618662Listing 4-18 - Modification du validateur du champ `status` 
     
    623667      public function configure() 
    624668      { 
    625         $statuses = ArticlePeer::getStatuses(); 
     669        // ... 
    626670         
    627         $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => $statuses)); 
    628  
     671        $statuses = ArticleTable::getStatuses(); 
     672         
     673        $this->widgetSchema['status']    = new sfWidgetFormSelect(array('choices' => $statuses)); 
    629674        $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys($statuses))); 
    630675      } 
     
    633678### Supprimer un champ 
    634679 
    635 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. 
     680La table `article` a trois colonnes spéciales, `created_at`, `updated_at` 
     681et `published_at`. Les deux premiers sont automatiquement gérée par Doctrine comme 
     682un comportement `timestampable`, le troisième que nous traiterons plus tard dans notre 
     683propre code. Nous devons donc les supprimer du formulaire comme le montre le Listing 4-19, 
     684pour éviter que les utilisateurs les modifient. 
    636685 
    637686Listing 4-19 - Suppression d'un champ 
     
    642691      public function configure() 
    643692      { 
     693        // ... 
     694       
    644695        unset($this->validatorSchema['created_at']); 
    645696        unset($this->widgetSchema['created_at']); 
     
    653704    } 
    654705 
    655 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. 
     706Afin de supprimer un champ, il est nécessaire de supprimer son validateur et son 
     707widget. Le Listing 4-20 montre comment il est possible de les supprimer tous les 
     708deux en une action, en utilisant le formulaire comme un tableau PHP. 
    656709 
    657710Listing 4-20 - Suppression d'un champ en utilisant le formulaire comme un tableau PHP 
     
    662715      public function configure() 
    663716      { 
     717        // ... 
     718       
    664719        unset($this['created_at'], $this['updated_at'], $this['published_at']); 
    665720      } 
     
    668723### Résumé 
    669724 
    670 Pour résumer, le Listing 4-21 et le Listing 4-22 montrent les formulaires `ArticleForm` et `AuthorForm` personnalisés. 
     725Pour résumer, le Listing 4-21 et le Listing 4-22 montrent les formulaires `ArticleForm` et 
     726`AuthorForm` personnalisés. 
    671727 
    672728Listing 4-21 - Formulaire `ArticleForm` 
     
    681737        // widgets 
    682738        $this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40)); 
    683         $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); 
     739        $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticleTable::getStatuses())); 
    684740        $this->widgetSchema['author_id']->setOption('query', $authorQuery); 
    685741 
     
    687743        $this->validatorSchema['slug']->setOption('required', false); 
    688744        $this->validatorSchema['content']->setOption('min_length', 5); 
    689         $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticlePeer::getStatuses()))); 
     745        $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticleTable::getStatuses()))); 
    690746        $this->validatorSchema['author_id']->setOption('query', $authorQuery); 
    691747 
     
    708764    } 
    709765 
    710 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 : 
    711  
    712   * 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. 
    713  
    714   * 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. 
    715  
    716 La prochaine section décrira la personnalisation des actions et des templates générés par la tâche `doctrine:generate-crud`. 
     766Utiliser la tâche `doctrine:build-forms` permet de générer la plupart 
     767des éléments en introspectant le modèle objet. Cette automatisation 
     768est utile pour plusieurs raisons : 
     769 
     770  * Cela rend la vie plus facile au développeur, lui épargnant un travail répétitif et 
     771    redondant. Il peut alors se concentrer sur la personnalisation des validateurs et des 
     772    widgets en fonction des règles métier spécifiques au projet. 
     773 
     774  * De plus, lorsque le schéma de base de données est mis à jour, les formulaires 
     775    générés seront automatiquement mis à jour. Le développeur aura juste à adapter 
     776    ses personnalisations. 
     777 
     778La prochaine section décrira la personnalisation des actions et des templates 
     779générés par la tâche `doctrine:generate-crud`. 
    717780 
    718781Sérialisation des formulaires 
    719782----------------------------- 
    720783 
    721 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`. 
     784La précédente section nous montre comment personnaliser les formulaires générés par la tâche 
     785`doctrine:build-forms`. Dans la section courante, nous allons personnaliser le cycle 
     786de vie des formulaires, en commençant par le code généré par 
     787`doctrine:generate-crud`. 
    722788 
    723789### Valeurs par défaut 
    724790 
    725 **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 : 
     791**Une instance de formulaire Doctrine est toujours reliée à un objet Doctrine**. 
     792L'objet Doctrine lié appartient toujours à la classe retournée par la méthode 
     793`getModelName()`. Par exemple, le formulaire `AuthorForm` peut seulement être 
     794lié à des objets appartenant à la classe `Author`. Ces objets sont soit 
     795des objets vides (une instance vide de la classe `Author`), soit les objets passés 
     796en premier argument au constructeur. Alors que le constructeur d'un formulaire 
     797« normal » prend un tableau de valeurs en premier argument, le constructeur 
     798d'un formulaire Doctrine prend un objet Doctrine. Cet objet est utilisé pour définir 
     799la valeur par défaut de chaque champ du fomulaire. La méthode `getObject()` retourne 
     800l'objet relié à l'instance courante, et la méthode `isNew()` permet de savoir si 
     801l'objet a été créé par le constructeur : 
    726802 
    727803    [php] 
     
    741817### Gestion du cycle de vie 
    742818 
    743 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. 
    744  
    745 Listing 4-23 - La méthode `executeEdit` du module `author` 
     819Comme nous l'avons observé au début de ce chapitre, les actions `new`, `edit` et 
     820`create`, montrée dans le listing 4-23, gèrent le cycle de vie du formulaire. 
     821 
     822Listing 4-23 - Les méthodes `executeNew`, `executeEdit`, `executeCreate` et 
     823`processForm` du module `author` 
    746824 
    747825    [php] 
     
    750828    { 
    751829      // ... 
    752  
    753       public function executeEdit($request) 
    754       { 
    755         $author = Doctrine::getTable('Author')->find($request->getParameter('id')); 
     830      public function executeNew(sfWebRequest $request) 
     831      { 
     832        $this->form = new AuthorForm(); 
     833      } 
     834 
     835      public function executeCreate(sfWebRequest $request) 
     836      { 
     837        $this->forward404Unless($request->isMethod('post')); 
     838 
     839        $this->form = new AuthorForm(); 
     840 
     841        $this->processForm($request, $this->form); 
     842 
     843        $this->setTemplate('new'); 
     844      } 
     845 
     846      public function executeEdit(sfWebRequest $request) 
     847      { 
     848        $this->forward404Unless($author = Doctrine::getTable('Author')->find($request->getParameter('id')), sprintf('Object author does not exist (%s).', $request->getParameter('id'))); 
    756849        $this->form = new AuthorForm($author); 
    757  
    758         if ($request->isMethod('post')) 
     850      } 
     851       
     852      protected function processForm(sfWebRequest $request, sfForm $form) 
     853      { 
     854        $form->bind($request->getParameter($form->getName())); 
     855        if ($form->isValid()) 
    759856        { 
    760           $this->form->bind($request->getParameter('author')); 
    761           if ($this->form->isValid()) 
    762           { 
    763             $author = $this->form->save(); 
    764  
    765             $this->redirect('author/edit?id='.$author->getId()); 
    766           } 
     857          $author = $form->save(); 
     858 
     859          $this->redirect('author/edit?id='.$author->getId()); 
    767860        } 
    768861      } 
    769862    } 
    770863 
    771 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 : 
     864Même si l'action `edit` ressemble aux actions que nous avons pu décrire dans 
     865les chapitres précédents, nous pouvons remarquer quelques différences : 
    772866 
    773867  * Un objet Doctrine de la classe `Author` est passé en premier paramètre du constructeur du formulaire : 
     
    777871        $this->form = new AuthorForm($author); 
    778872 
    779   * 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`) : 
     873  * Le format des attributs `name` des widgets est automatiquement défini de manière à 
     874    récupérer les données soumises dans un tableau PHP nommé d'après la table liée (`author`) : 
    780875 
    781876        [php] 
    782         $this->form->bind($request->getParameter('author')); 
    783  
    784   * Lorsque le formulaire est valide, un appel à la méthode `save()` crée ou met à jour les objets Doctrine reliés au formulaire : 
     877        $form->bind($request->getParameter($form->getName())); 
     878 
     879  * Lorsque le formulaire est valide, un appel à la méthode `save()` crée ou met à jour 
     880    les objets Doctrine reliés au formulaire : 
    785881 
    786882        [php] 
    787         $author = $this->form->save(); 
     883        $author = $form->save(); 
    788884 
    789885### Créer et modifier un objet Doctrine 
    790886 
    791 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` : 
     887Le code du Listing 4-23 gère en une seule méthode la création et la modification d'objets de la 
     888classe `Author` : 
    792889 
    793890  * Création d'un nouvel objet `Author` : 
    794891 
    795       * L'action `index` est appelée sans paramètre `id` (`$request->getParameter('id')` vaut `null`) 
    796  
    797       * L'appel à la méthode `find()` renvoie dont `null` 
     892      * L'action `create` est appelée 
    798893 
    799894      * L'objet `form` est alors lié à un objet Doctrine `Author` vide 
    800895 
    801       * L'appel à `$this->form->save()` crée un nouvel objet `Author` en conséquence quand un formulaire valide est soumis 
     896      * L'appel à `$form->save()` crée un nouvel objet `Author` en conséquence 
     897        quand un formulaire valide est soumis 
    802898 
    803899  * Modification d'un objet Author` existant : 
    804900 
    805       * L'action `index` est appelée avec un paramètre `id` (`$request->getParameter('id')` représentant la clé primaire de l'objet `Author` à modifier) 
    806  
    807       * L'appel à la méthode `find()` retourne l'objet `Author` associé à la clé primaire 
     901      * L'action `update` est appelée avec un paramètre `id` 
     902        (`$request->getParameter('id')` représentant la clé primaire de 
     903        l'objet `Author` à modifier) 
     904 
     905      * L'appel à la méthode `find()` retourne l'objet `Author` associé à 
     906        la clé primaire 
    808907 
    809908      * L'objet `form` est donc lié à l'objet précédemment trouvé 
    810909 
    811       * L'appel à `$this->form->save()` met à jour l'objet `Author` quand un formulaire valide est soumis 
     910      * L'appel à `$form->save()` met à jour l'objet `Author` quand un formulaire valide 
     911        est soumis 
    812912 
    813913### La méthode `save()` 
     
    843943### Gestion des envois de fichiers 
    844944 
    845 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. 
    846  
    847 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. 
     945La méthode `save()` met automatiquement à jour les objets Doctrine, mais ne peut pas 
     946gérer les autres éléments tels que les envois de fichiers. 
     947 
     948Voyons comment attacher un fichier à chaque article. Les fichiers sont enregistrés dans 
     949le répertoire `web/uploads` et une référence vers le fichier est gardée dans le champ 
     950`file` de la table `article`, comme le montre le Listing 4-24. 
    848951 
    849952Listing 4-24 - Schéma pour la table `article` avec le fichier associé 
    850953 
    851954    [yml] 
    852     // config/schema.yml 
    853     doctrine: 
    854       article: 
    855         // ... 
    856         file: string(255) 
     955    // config/doctrine/schema.yml 
     956    Article: 
     957      // ... 
     958      file: string(255) 
    857959 
    858960Aprè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 : 
     
    860962    $ ./symfony doctrine:build-all 
    861963 
    862 >**Attention** 
    863 >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. 
     964>**Caution** 
     965>Soyez conscients que la tâche `doctrine:build-all` supprime toutes les tables du schéma 
     966>et les recrée. Les données dans les tables sont donc réécrites. C'est pourquoi il est 
     967>important de créer des données de test (`fixtures`) qui pourront être rechargées à 
     968>chaque modification du modèle. 
    864969 
    865970Le Listing 4-25 montre comment modifier la classe `ArticleForm` afin de relier un widget et un validateur au champ `file`. 
     
    879984    } 
    880985 
    881 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). 
     986Comme pour chaque formulaire permettant l'envoi d'un fichier, n'oubliez pas d'ajouter 
     987l'attribut `enctype` à la balise `form` dans le template (voyez le Chapitre 2 pour 
     988plus d'informations concernant la gestion d'envois de fichiers). 
     989 
     990>**TIP** 
     991>Lorsque vous créez votre modèle de formulaire, vous pouvez vérifier si le formulaire 
     992>contient des champs de fichier, et ajouter l'attribut `enctype` automatiquement : 
     993
     994>     [PHP] 
     995>     <?php if ($form->isMultipart() echo 'enctype="multipart/form-data" '; ?> 
     996
     997>Ce code est automatiquement ajoutée lorsque votre formulaire est créé par la tâche generate-crud. 
    882998 
    883999Le 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`. 
     
    9451061>Les actions générées par la tâche `doctrine:generate-crud` ne devraient généralement pas être modifiées. 
    9461062> 
    947 >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. 
    948 
    949 >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 : 
     1063>La logique que vous pourriez ajouter dans l'action `edit`, particulièrement durant la sérialisation du 
     1064>formulaire, doit souvent être déplacée dans les classes du modèle ou dans les classes du formulaire. 
     1065
     1066>Nous avons juste vu un exemple de refactorisation dans la classe formulaire afin 
     1067>d'effectuer un stockage de fichier. Prenons un autre exemple lié au modèle. Le 
     1068>formulaire `ArticleForm` a un champ `slug`. Nous avons vu que ce champ devrait 
     1069>être calculé automatiquement à partir du champ `title`, et qu'il pourrait 
     1070>éventuellement être spécifié par l'utilisateur. Cette logique ne dépend pas du 
     1071>formulaire. Elle appartient donc au modèle, comme le montre le code suivant : 
    9501072> 
    9511073>     [php] 
     
    9681090>     } 
    9691091> 
    970 >Le but principal de ces refactorisations est de respecter la séparation dans les couches applicatives, particulièrement la réutilisation du code. 
     1092>Le but principal de ces refactorisations est de respecter la séparation dans les couches applicatives, 
     1093>particulièrement la réutilisation du code. 
    9711094 
    9721095### Personnalisation de la méthode `doSave()` 
     
    9741097Nous 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. 
    9751098 
    976 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é. 
     1099Le Listing 4-28 nous montre comment utiliser la méthode `doSave()` pour insérer dans la transaction globale le code enregistrant le fichier envoyé. 
    9771100 
    9781101Listing 4-28 - Surcharge de la méthode `doSave()` dans le formulaire `ArticleForm` 
     
    10151138      // ... 
    10161139 
    1017       public function updateObject(
    1018       { 
    1019         $object = parent::updateObject(); 
     1140      public function updateObject($values = null
     1141      { 
     1142        $object = parent::updateObject($values); 
    10201143 
    10211144        $object->setFile(str_replace(sfConfig::get('sf_upload_dir').'/', '', $object->getFile()));