Author: forresst
Date: 2010-01-18 12:27:14 +0100 (Mon, 18 Jan 2010)
New Revision: 26804
Added:
doc/branches/1.2/jobeet/fr/10.txt
Log:
[doc-fr][1.2] Add doc in french, jobeet/10 rev:en/20720
Added: doc/branches/1.2/jobeet/fr/10.txt
===================================================================
--- doc/branches/1.2/jobeet/fr/10.txt (rev 0)
+++ doc/branches/1.2/jobeet/fr/10.txt 2010-01-18 11:27:14 UTC (rev 26804)
@@ -0,0 +1,992 @@
+Jour 10 : Les formulaires
+=========================
+
+La deuxième semaine de Jobeet a pris un bon départ avec l'introduction du
framework
+de test de symfony. Nous allons continuer aujourd'hui avec le framework de
formulaire
+
+Le framework de formulaire
+--------------------------
+
+Tout site Web a des ~formulaires|Formulaires~ : du simple formulaire de
contact à celui avec
+beaucoup plus de champs. L'écriture de formulaire est aussi une des tâches les
plus complexes et l'une
+des plus fastidieuses pour un développeur web : vous devez écrire le
formulaire HTML, implémenter des
+règles de validation pour chaque champ, traiter les valeurs pour les stocker
dans une base de données,
+afficher les messages d'erreur, repeupler les champs dans le cas d'erreurs, et
bien plus encore ...
+
+Bien sûr, au lieu de réinventer la roue, encore et encore, symfony fournit
+un framework pour faciliter la gestion de formulaire. Le framework de
formulaire
+est composé de trois parties :
+
+ * **validation**: Le sous-framework de ~validation|Validation~ fournit les
classes pour valider
+ les saisies (entier, chaine, adresse email, ...)
+ * **widgets**: Le sous-framework de ~widget|Widgets~ fournit les classes
pour la production
+ des champs HTML (input, textarea, select, ...)
+ * **forms**: Les classes ~formulaire|Formulaires~ représentent des
formulaires faits de widgets et
+ de validateurs et fournit des méthodes pour aider la
gestion du formulaire.
+ Chaque champs du formulaire a son propre validateur et son
propre widget.
+
+Les formulaires
+---------------
+
+Un formulaire de symfony est une classe composé de champs. Chaque champ a un
nom, un ~validateur|Validateurs~
+et un ~widget|Widgets~. Un simple `ContactForm` peut être défini avec la
classe suivante :
+
+ [php]
+ class ContactForm extends sfForm
+ {
+ public function configure()
+ {
+ $this->setWidgets(array(
+ 'email' => new sfWidgetFormInput(),
+ 'message' => new sfWidgetFormTextarea(),
+ ));
+
+ $this->setValidators(array(
+ 'email' => new sfValidatorEmail(),
+ 'message' => new sfValidatorString(array('max_length' => 255)),
+ ));
+ }
+ }
+
+Les champs du formulaire sont configurés dans la méthode `configure()`, en
utilisant
+les méthodes `setValidators()` et `setWidgets()`.
+
+>**TIP**
+>Le frameework de formulaire est livré avec un lot de
+>[widgets](http://www.symfony-project.org/api/1_4/widget) et
+>[validators](http://www.symfony-project.org/api/1_4/validator). L'API les
+>décrit assez largement avec toutes les options, les erreurs et les messages
+>d'erreur par défaut.
+
+Les noms des classes des widgets et des validateurs sont assez explicites : le
champ `email`
+sera rendu par une balise HTML `<input>` (`sfWidgetFormInputText`) et sera
+validé par une adresse email (`sfValidatorEmail`). Le champ `message` sera
+rendu par une balise `<textarea>` (`sfWidgetFormTextarea`), et doit être une
+chaine d'au plus 255 caractères (`sfValidatorString`).
+
+Par défaut, tous les champs sont ~obligatoires|Champs du formulaire
obligatoire~, car
+la valeur par défaut pour l'option `required` est `true`. Ainsi, la définition
de la validation
+pour `email` est équivalente à `new sfValidatorEmail(array('required' =>
true))`.
+
+>**TIP**
+>Vous pouvez fusionner un formulaire dans un autre en utilisant la méthode
`mergeForm()`,
+>ou incorporer un formulaire à l'aide de la méthode `embedForm()` :
+>
+> [php]
+> $this->mergeForm(new AnotherForm());
+> $this->embedForm('name', new AnotherForm());
+
+Les formulmaires de ##ORM##
+---------------------------
+
+La plupart du temps, un formulaire doit être sérialisé à la base de données.
Comme
+symfony sait déjà tout sur le modèle de votre base de données, il peut générer
automatiquement
+des formulaires basés sur ces informations. En fait, lorsque vous avez lancé
la tâche
+`propel:build-all` au cours du jour 3, symfony a automatiquement appelé la
tâche
+`propel:build-forms` :
+
+ $ php symfony propel:build-forms
+
+La tâche `propel:build-forms` génère des classes de formulaires dans le
répertoire
+`lib/form/`. L'organisation de ces fichiers générés est semblable à celle de
+`lib/model/`. Chaque classe du modèle a une classe de formulaire
correspondante (par
+exemple `JobeetJob` a `JobeetJobForm`), qui est vide par défaut car il hérite
d'une
+classe de base :
+
+ [php]
+<propel>
+ // lib/form/JobeetJobForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetJobForm.class.php
+</doctrine>
+ class JobeetJobForm extends BaseJobeetJobForm
+ {
+ public function configure()
+ {
+ }
+ }
+
+<propel>
+>**TIP**
+>En parcourant les fichiers générés dans le sous-répertoire `lib/form/base/`,
vous
+>pourrez voir de nombreux exemples d'usage de symfony intégré dans les widgets
et
+>les validateurs.
+</propel>
+<doctrine>
+>**TIP**
+>En parcourant les fichiers générés dans le sous-répertoire
`lib/form/doctrine/base/`,
+>vous pourrez voir de nombreux exemples d'usage de symfony intégré dans les
widgets et
+>les validateurs.
+</doctrine>
+
+### Personnalisation du formulaire d'emploi
+
+Le formulaire d'emploi est un parfait exemple pour apprendre la
+~personnalisation du formulaires|Formulaires (Personnalisation)~. Voyons
comment le personnaliser, étape par étape.
+
+D'abord, changez le lien "Post a Job" dans la mise en page pour être en mesure
de vérifier les
+modifications directement dans votre navigateur :
+
+ [php]
+ <!-- apps/frontend/templates/layout.php -->
+ <a href="<?php echo url_for('@job_new') ?>">Post a Job</a>
+
+Par défaut, un formulaire ##ORM## affiche des champs pour toutes les colonnes
de la table. Mais
+pour le formulaire d'emploi, certains d'entre eux ne doit pas être modifiable
par l'utilisateur final.
+La suppression des champs à partir d'un formulaire est aussi simple que d'en
désactiver :
+
+ [php]
+<propel>
+ // lib/form/JobeetJobForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetJobForm.class.php
+</doctrine>
+ class JobeetJobForm extends BaseJobeetJobForm
+ {
+ public function configure()
+ {
+ unset(
+ $this['created_at'], $this['updated_at'],
+ $this['expires_at'], $this['is_activated']
+ );
+ }
+ }
+
+La désactivation d'un champ signifie que le widget et le validateur du champs
sont supprimés.
+
+La configuration du formulaire doit parfois être plus précis que
l'introspection
+à partir du schéma de la base de données. Par exemple, la colonne `email` est
un
+`varchar` dans le schéma, mais nous avons besoin de cette colonne pour être
validé comme
+un email. Changeons la valeur par défaut `sfValidatorString` par
`sfValidatorEmail` :
+
+ [php]
+<propel>
+ // lib/form/JobeetJobForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetJobForm.class.php
+</doctrine>
+ public function configure()
+ {
+ // ...
+
+ $this->validatorSchema['email'] = new sfValidatorEmail();
+ }
+
+Le remplacement de la validation par défaut n'est pas toujours la meilleure
solution,
+comme les règles de validation par défaut introspectés depuis le schéma de la
base de données
+sont perdues (`new sfValidatorString(array('max_length' => 255))`). Il est
presque toujours
+préférable d'ajouter le nouveau validateur à celui déjà existant à l'aide du
validateur
+spécial `sfValidatorAnd` :
+
+ [php]
+<propel>
+ // lib/form/JobeetJobForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetJobForm.class.php
+</doctrine>
+ public function configure()
+ {
+ // ...
+
+ $this->validatorSchema['email'] = new sfValidatorAnd(array(
+ $this->validatorSchema['email'],
+ new sfValidatorEmail(),
+ ));
+ }
+
+Le validateur `sfValidatorAnd` prend un tableau de validateurs qui doivent
tous passer
+pour que cette valeur soit valide. L'astuce ici est de faire référence au
validateur
+actuel ($this->validatorSchema['email']), et d'ajouter le nouveau.
+
+>**NOTE**
+>Vous pouvez également utiliser le validateur `sfValidatorOr` pour forcer une
+>valeur à passer pour au moins un validateur. Et bien sûr, vous pouvez
mélanger et
+>fusionner les validateurs `sfValidatorAnd` et `sfValidatorOr` pour créer des
validateurs
+>complexes sur base de booléen.
+
+Même si la colonne `type` est également un `varchar` dans le schéma, nous
voulons sa
+valeur pour se limiter à une liste de choix : temps plein, temps partiel, ou
freelance.
+
+<propel>
+D'abord, nous allons définir les valeurs possibles dans `JobeetJobPeer` :
+
+ [php]
+ // lib/model/JobeetJobPeer.php
+ class JobeetJobPeer extends BaseJobeetJobPeer
+ {
+ static public $types = array(
+ 'full-time' => 'Full time',
+ 'part-time' => 'Part time',
+ 'freelance' => 'Freelance',
+ );
+
+ // ...
+ }
+</propel>
+<doctrine>
+D'abord, nous allons définir les valeurs possibles dans `JobeetJobTable` :
+
+ [php]
+ // lib/model/doctrine/JobeetJobTable.class.php
+ class JobeetJobTable extends Doctrine_Table
+ {
+ static public $types = array(
+ 'full-time' => 'Full time',
+ 'part-time' => 'Part time',
+ 'freelance' => 'Freelance',
+ );
+
+ public function getTypes()
+ {
+ return self::$types;
+ }
+
+ // ...
+ }
+</doctrine>
+
+Puis, utilisez `sfWidgetFormChoice` pour le widget `type` :
+
+ [php]
+ $this->widgetSchema['type'] = new sfWidgetFormChoice(array(
+<propel>
+ 'choices' => JobeetJobPeer::$types,
+</propel>
+<doctrine>
+ 'choices' => Doctrine::getTable('JobeetJob')->getTypes(),
+</doctrine>
+ 'expanded' => true,
+ ));
+
+`sfWidgetFormChoice` représente un ~widget de choix~ qui peut être affiché par
un
+widget différent selon certaines options de configuration (`expanded` et
+`multiple`) :
+
+ * Liste déroulante (`<select>`) : `array('multiple' => false, 'expanded' =>
false)`
+ * Boîte déroulante (`<select multiple="multiple">`) : `array('multiple' =>
true, 'expanded' => false)`
+ * Liste de radiobuttons : `array('multiple' => false, 'expanded' => true)`
+ * List de checkboxes : `array('multiple' => true, 'expanded' => true)`
+
+>**NOTE**
+>Si vous voulez que l'un des radiobutton soit sélectionné par défaut
(`full-time`
+>par exemple), vous pouvez changer la valeur par défaut dans le schéma de base
de données.
+
+Même si vous pensez que personne peut soumettre un valeur non valide, un
pirate peut facilement
+contourner les choix du widget en utilisant des outils comme
[curl](http://curl.haxx.se/) ou
+la [barre d'outils de développeur web de
Firefox](http://chrispederick.com/work/web-developer/).
+Changeons le validateur pour restreindre les choix possibles :
+
+<propel>
+ [php]
+ $this->validatorSchema['type'] = new sfValidatorChoice(array(
+ 'choices' => array_keys(JobeetJobPeer::$types),
+ ));
+</propel>
+<doctrine>
+ [php]
+ $this->validatorSchema['type'] = new sfValidatorChoice(array(
+ 'choices' => array_keys(Doctrine::getTable('JobeetJob')->getTypes()),
+ ));
+</doctrine>
+
+Comme la colonne `logo` va stocker le nom du fichier du logo associé à
l'emploi,
+nous devons changer le widget en une balise ~file input|File Input~ :
+
+ [php]
+ $this->widgetSchema['logo'] = new sfWidgetFormInputFile(array(
+ 'label' => 'Company logo',
+ ));
+
+Pour chaque champ, symfony génère automatiquement un ~label|Labels de
formulaire~ (qui
+sera utilisé dans la balise `<label>` rendue). Ceci peut être modifié avec
l'option `label`.
+
+Vous pouvez également modifier les labels d'un batch avec la méthode
`setLabels()`
+du tableau de widget :
+
+ [php]
+ $this->widgetSchema->setLabels(array(
+ 'category_id' => 'Category',
+ 'is_public' => 'Public?',
+ 'how_to_apply' => 'How to apply?',
+ ));
+
+Nous devons également changer le validateur par défaut :
+
+ [php]
+ $this->validatorSchema['logo'] = new sfValidatorFile(array(
+ 'required' => false,
+ 'path' => sfConfig::get('sf_upload_dir').'/jobs',
+ 'mime_types' => 'web_images',
+ ));
+
+`sfValidatorFile` est assez intéressant car il fait un certain nombre de
choses :
+
+ * Valider que le fichier téléchargé est une image dans un format web
(`mime_types`)
+ * Renomme le fichier en quelque chose d'unique
+ * Stocke le fichier dans le `path` donné
+ * Met à jour la colonne `logo` avec le nom généré
+
+>**NOTE**
+>Vous devez créer le répertoire logo (`web/uploads/jobs/`) et vérifier qu'il
est
+>accessible en écriture par le serveur web.
+
+Comme le validateur enregistrera le chemin relatif dans la base de données,
changer le
+chemin utilisé dans le template `showSuccess` :
+
+ [php]
+ // apps/frontend/modules/job/templates/showSuccess.php
+ <img src="/uploads/jobs/<?php echo $job->getLogo() ?>" alt="<?php echo
$job->getCompany() ?> logo" />
+
+>**TIP**
+>Si une méthode `generateLogoFilename()` existe dans le modèle, il sera appelé
+>par le validateur et le résultat remplacera le nom de fichier `logo` généré
par
+>défaut. La méthode est donné à l'objet `sfValidatedFile` comme argument.
+
+Tout comme vous pouvez remplacer le label généré de n'importe quel champ, vous
pouvez aussi
+définir un ~message d'aide|Formulaires (Aide)~. Ajoutons en un pour la colonne
`is_public`
+pour mieux expliquer sa signification :
+
+ [php]
+ $this->widgetSchema->setHelp('is_public', 'Whether the job can also be
published on affiliate websites or not.');
+
+La classe `JobeetJobForm` finale se lit comme suit :
+
+ [php]
+<propel>
+ // lib/form/JobeetJobForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetJobForm.class.php
+</doctrine>
+ class JobeetJobForm extends BaseJobeetJobForm
+ {
+ public function configure()
+ {
+ unset(
+ $this['created_at'], $this['updated_at'],
+ $this['expires_at'], $this['is_activated']
+ );
+
+ $this->validatorSchema['email'] = new sfValidatorAnd(array(
+ $this->validatorSchema['email'],
+ new sfValidatorEmail(),
+ ));
+
+ $this->widgetSchema['type'] = new sfWidgetFormChoice(array(
+<propel>
+ 'choices' => JobeetJobPeer::$types,
+</propel>
+<doctrine>
+ 'choices' => Doctrine::getTable('JobeetJob')->getTypes(),
+</doctrine>
+ 'expanded' => true,
+ ));
+ $this->validatorSchema['type'] = new sfValidatorChoice(array(
+<propel>
+ 'choices' => array_keys(JobeetJobPeer::$types),
+</propel>
+<doctrine>
+ 'choices' => array_keys(Doctrine::getTable('JobeetJob')->getTypes()),
+</doctrine>
+ ));
+
+ $this->widgetSchema['logo'] = new sfWidgetFormInputFile(array(
+ 'label' => 'Company logo',
+ ));
+
+ $this->widgetSchema->setLabels(array(
+ 'category_id' => 'Category',
+ 'is_public' => 'Public?',
+ 'how_to_apply' => 'How to apply?',
+ ));
+
+ $this->validatorSchema['logo'] = new sfValidatorFile(array(
+ 'required' => false,
+ 'path' => sfConfig::get('sf_upload_dir').'/jobs',
+ 'mime_types' => 'web_images',
+ ));
+
+ $this->widgetSchema->setHelp('is_public', 'Whether the job can also be
published on affiliate websites or not.');
+ }
+ }
+
+### Le template du formulaire
+
+Maintenant que la classe de formulaire a été personnalisé, nous avons besoin de
+l'afficher. Le ~Template|Templates~ du formulaire est le même, que vous voulez
créer
+un nouvel emploi ou modifier un existant. En fait, les deux Templates
`newSuccess.php`
+et `editSuccess.php`sont assez similaires:
+
+ [php]
+ <!-- apps/frontend/modules/job/templates/newSuccess.php -->
+ <?php use_stylesheet('job.css') ?>
+
+ <h1>Post a Job</h1>
+
+ <?php include_partial('form', array('form' => $form)) ?>
+
+>**NOTE**
+>Si vous n'avez pas ajouté de la feuille de style `job` pour le moment, il est
+>temps de le faire dans les deux Templates (`<?php use_stylesheet('job.css')
?>`).
+
+Le formulaire est lui-même rendu dans le ~partial|Partial Templates~ `_form`.
+Remplacez le contenu du partial généré `_form` par le code suivant :
+
+ [php]
+ <!-- apps/frontend/modules/job/templates/_form.php -->
+ <?php include_stylesheets_for_form($form) ?>
+ <?php include_javascripts_for_form($form) ?>
+
+ <?php echo form_tag_for($form, '@job') ?>
+ <table id="job_form">
+ <tfoot>
+ <tr>
+ <td colspan="2">
+ <input type="submit" value="Preview your job" />
+ </td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <?php echo $form ?>
+ </tbody>
+ </table>
+ </form>
+
+Les helpers `include_javascripts_for_form()` et
`include_stylesheets_for_form()`
+incluent des dépendances de JavaScript et de feuille de style nécessaires pour
les
+widgets du formulaire.
+
+>**TIP**
+>Même si le formulaire emploi n'a pas besoin de JavaScript ou d'un fichier de
style, c'est
+>une bonne habitude de garder l'appels de ces helpers "juste au cas où". Il
peut sauver votre
+>journée plus tard, si vous décidez de changer un widget qui a besoin de
quelques ~JavaScript~ ou
+>d'une ~feuille de style|Feuille de style~ spécifique.
+
+Le ~helper `form_tag_for()`~ génère une ~balise|Formulaires (HTML)~ `<form>`
pour le
+formulaire et la route donnés et modifie les ~méthodes HTTP|Méthodes HTTP
POST~ en
+~`POST`|POST (Méthode HTTP)~ ou en ~`PUT`|PUT (Méthode HTTP)~ PUT selon si
l'objet est
+nouveau ou non. Il s'occupe aussi de l'attribut `~multipart|Formulaires
+(Multipart)~` si le formulaire a aucune balise input file.
+
+Finalement, le `<?php echo $form ?>` rend les widgets du formulaire.
+
+>**SIDEBAR**
+>Personnalisation de l'aspect d'un formulaire
+>
+>Par défaut, le `<?php echo $form ?>` rend les widgets du formulaire en tant
que lignes de tableau.
+>
+>La plupart du temps, vous aurez besoin de personnaliser la présentation de vos
+>formulaires. L'objet du formulaire fournit de nombreuses méthodes utiles pour
cette
+>~personnalisation|Personnalisation~ :
+>
+> | Méthode | Description
+> | ---------------------- | -------------------------------------------------
+> | `render()` | Rend le formulaire (equivalent à la sortie de
+> | | `echo $form`)
+> | `renderHiddenFields()` | Rend les champs masqués
+> | `hasErrors()` | Retourne `true` si le formulaire a quelques
erreurs
+> | `hasGlobalErrors()` | Retourne `true` si le formulaire a des erreurs
globales
+> | `getGlobalErrors()` | Retourne un tableau des erreurs globales
+> | `renderGlobalErrors()` | Rend les erreurs globales
+>
+>Le formulaire se comporte aussi comme un tableau de champs. Vous pouvez
accéder
+>au champ `company` avec `$form['company']`. L'objet retourné fournit des
méthodes
+>pour rendre chaque élément du champs :
+>
+> | Méthode | Description
+> | --------------- | ---------------------------------------
+> | `renderRow()` | Rend la ligne du champ
+> | `render()` | Rend le widget du champ
+> | `renderLabel()` | Rend le label du champ
+> | `renderError()` | Rend les messages d'érreur du champ s'il y'en a
+> | `renderHelp()` | Rend le message d'aide du champ
+>
+>L'instruction `echo $form` est équivalent à :
+>
+> [php]
+> <?php foreach ($form as $widget): ?>
+> <?php echo $widget->renderRow() ?>
+> <?php endforeach; ?>
+
+### L'action du formulaire
+
+Nous avons maintenant une classe de formulaire et un template qui fait le
rendu.
+Maintenant, il est temps de le faire réellement fonctionner avec certaines
~action|Action~s.
+
+Le formulaire emploi est géré par cinq méthodes dans le module `job` :
+
+ * **new**: Affiche un formulaire non renseigné pour créer un nouvel
emploi
+ * **edit**: Affiche un formulaire pour modifier un emploi existant
+ * **create**: Crée un nouvel emploi avec les valeurs soumises par
l'utilisateur
+ * **update**: Met à jour un emploi avec les valeurs soumises par
l'utilisateur
+ * **processForm**: Appelé par `create` et `update`, il traite le formulaire
+ (validation, repopulation du formulaire et sérialisation
vers
+ la base de données)
+
+Tous les formulaires ont le cycle de vie suivant :
+
+
+
+Comme nous avons créé une collection de route ##ORM## il y a 5 jours pour le
module `job`,
+nous pouvons simplifier le code pour les méthodes de gestion de formulaire :
+
+ [php]
+ // apps/frontend/modules/job/actions/actions.class.php
+ public function executeNew(sfWebRequest $request)
+ {
+ $this->form = new JobeetJobForm();
+ }
+
+ public function executeCreate(sfWebRequest $request)
+ {
+ $this->form = new JobeetJobForm();
+ $this->processForm($request, $this->form);
+ $this->setTemplate('new');
+ }
+
+ public function executeEdit(sfWebRequest $request)
+ {
+ $this->form = new JobeetJobForm($this->getRoute()->getObject());
+ }
+
+ public function executeUpdate(sfWebRequest $request)
+ {
+ $this->form = new JobeetJobForm($this->getRoute()->getObject());
+ $this->processForm($request, $this->form);
+ $this->setTemplate('edit');
+ }
+
+ public function executeDelete(sfWebRequest $request)
+ {
+ $request->checkCSRFProtection();
+
+ $job = $this->getRoute()->getObject();
+ $job->delete();
+
+ $this->redirect('job/index');
+ }
+
+ protected function processForm(sfWebRequest $request, sfForm $form)
+ {
+ $form->bind(
+ $request->getParameter($form->getName()),
+ $request->getFiles($form->getName())
+ );
+
+ if ($form->isValid())
+ {
+ $job = $form->save();
+
+ $this->redirect($this->generateUrl('job_show', $job));
+ }
+ }
+
+Lorsque vous naviguez vers la page `/job/new`, une nouvelle instance du
~formulaire|Formulaires~
+est créé et transmise au Template (action `new`).
+
+Lorsque l'utilisateur soumet le formulaire (action `create`), le formulaire
est lié (méthode
+`bind()`) avec les valeurs soumises par l'utilisateur et la validation est
déclenchée.
+
+Une fois que le formulaire est lié, il est possible de vérifier sa validité en
utilisant
+la méthode `isValid()` : si le formulaire est valide (elle retourne `true`),
l'emploi est sauvé
+dans la base de données (`$form->save()`), et l'utilisateur est
~redirigé|Redirection~ vers la
+page de prévisualisation d'emploi, sinon, le template `newSuccess.php`
s'affiche à nouveau avec
+les valeurs soumises par l'utilisateur et les messages d'erreur associés.
+
+>**TIP**
+>La méthode `setTemplate()` change le ~Template|Templates~ utilisé pour une
action donnée. Si
+>le formulaire soumis n'est pas valide, les méthodes `create` et `update`
utilisent le même
+>template respectif que l'action `new` et `edit` pour ré-afficher le
formulaire avec
+>les messages d'erreur.
+
+La modification d'un emploi existant est assez similaire. La seule différence
+entre l'action `new` et `edit` est que l'objet job à modifier, est passé comme
+premier argument au constructeur du formulaire. Cet objet sera utilisé pour les
+valeurs par défaut de widget dans le Template (les valeurs par défaut sont un
objet
+pour le formulaire ##ORM##, mais un simple tableau pour des formulaires
simples).
+
+Vous pouvez également définir des valeurs par défaut pour le formulaire de
création.
+Une solution est de déclarer les valeurs dans le schéma de base de données.
Une autre
+est de passer un objet `Job` pré-modifié au constructeur du formulaire.
+
+Changer la méthode `executeNew()` pour définir `full-time` comme étant la
valeur
+par défaut pour la colonne `type` :
+
+ [php]
+ // apps/frontend/modules/job/actions/actions.class.php
+ public function executeNew(sfWebRequest $request)
+ {
+ $job = new JobeetJob();
+ $job->setType('full-time');
+
+ $this->form = new JobeetJobForm($job);
+ }
+
+>**NOTE**
+>Lorsque le formulaire est lié, les valeurs par défaut sont remplacées par
celles soumises
+>par l'utilisateur. Les valeurs soumises par l'utilisateur seront utilisées
pour le repeuplement
+>du formulaire lorsque ce dernier est à nouveau affichée en cas d'erreurs de
validation.
+
+### Protection du formulaire d'emploi avec un jeton (token)
+
+Tout doit fonctionner correctement maintenant. Pour l'instant, l'utilisateur
doit saisir le
+jeton pour l'emploi. Mais le jeton de l'emploi doit être généré
automatiquement quand un nouvel
+emploi est créé, car nous ne pouvons pas se fier à l'utilisateur pour fournir
un jeton unique.
+
+Mettez à jour la méthode `save()` de `JobeetJob` pour ajouter la logique qui
+génère le jeton avant qu'un nouvel emploi soit enregistré :
+
+ [php]
+<propel>
+ // lib/model/JobeetJob.php
+ public function save(PropelPDO $con = null)
+</propel>
+<doctrine>
+ // lib/model/doctrine/JobeetJob.class.php
+ public function save(Doctrine_Connection $con = null)
+</doctrine>
+ {
+ // ...
+
+ if (!$this->getToken())
+ {
+ $this->setToken(sha1($this->getEmail().rand(11111, 99999)));
+ }
+
+<propel>
+ return parent::save($con);
+</propel>
+<doctrine>
+ return parent::save($conn);
+</doctrine>
+ }
+
+Vous pouvez maintenant supprimer le champ `token` du formulaire :
+
+ [php]
+<propel>
+ // lib/form/JobeetJobForm.class.php
+</propel>
+<doctrine>
+ // lib/form/doctrine/JobeetJobForm.class.php
+</doctrine>
+ class JobeetJobForm extends BaseJobeetJobForm
+ {
+ public function configure()
+ {
+ unset(
+ $this['created_at'], $this['updated_at'],
+ $this['expires_at'], $this['is_activated'],
+ $this['token']
+ );
+
+ // ...
+ }
+
+ // ...
+ }
+
+Si vous vous souvenez des histoires d'utilisateurs du jour 2, un emploi peut
être modifié que si
+l'utilisateur connaît le jeton associé. À l'heure actuelle, il est assez
facile de modifier ou de
+supprimer n'importe quel emploi, en essayant juste de deviner l'URL. Car l'URL
de edit est
+`/job/ID/edit`, où `ID` est la ~clé primaire|Clé primaire~ de l'emploi.
+
+Par défaut, une route ~`sfPropelRouteCollection`~ génère des URL avec la clé
primaire,
+mais elle peut être changée par n'importe quelle colonne unique en faisant
passer
+l'option `column` :
+
+ [yml]
+ # apps/frontend/config/~routing|Routing~.yml
+ job:
+ class: sfPropelRouteCollection
+ options: { model: JobeetJob, column: token }
+ requirements: { token: \w+ }
+
+Notez que nous avons également modifié le paramètre requirements `token` pour
faire correspondre
+n'importe quelle chaîne car le requirements de symfony par défaut est `\d+`
pour la clé unique.
+
+Maintenant, toutes les routes liées aux emplois, à l'exception des
`job_show_user`, incorporent
+le jeton. Par exemple, la route pour modifier un emploi est maintenant sur le
modèle suivant :
+
+ http://jobeet.localhost/job/TOKEN/edit
+
+Vous aurez aussi besoin de changer le lien "Edit" dans le Template
`showSuccess` :
+
+ [php]
+ <!-- apps/frontend/modules/job/templates/showSuccess.php -->
+ <a href="<?php echo url_for('job_edit', $job) ?>">Edit</a>
+
+La page de prévisualisation
+---------------------------
+
+La page de prévisualisation est la même que l'affichage de la page emploi.
Grâce au
+~routage|Routage~, si l'utilisateur arrive avec le bon jeton, il sera
accessible
+dans le paramètre de la requête `token`.
+
+Si l'utilisateur entre dans l'URL sous forme de jeton, nous allons ajouter une
barre
+d'admininistrateur en haut. Au début du Template `showSuccess`, ajoutez un
partial pour
+mettre la barre d'administrateur et supprimer le lien `edit` en bas :
+
+ [php]
+ <!-- apps/frontend/modules/job/templates/showSuccess.php -->
+ <?php if ($sf_request->getParameter('token') == $job->getToken()): ?>
+ <?php include_partial('job/admin', array('job' => $job)) ?>
+ <?php endif; ?>
+
+Ensuite, créez le partial `_admin` :
+
+ [php]
+ <!-- apps/frontend/modules/job/templates/_admin.php -->
+ <div id="job_actions">
+ <h3>Admin</h3>
+ <ul>
+ <?php if (!$job->getIsActivated()): ?>
+ <li><?php echo link_to('Edit', 'job_edit', $job) ?></li>
+ <li><?php echo link_to('Publish', 'job_edit', $job) ?></li>
+ <?php endif; ?>
+ <li><?php echo link_to('Delete', 'job_delete', $job, array('method' =>
'delete', 'confirm' => 'Are you sure?')) ?></li>
+ <?php if ($job->getIsActivated()): ?>
+ <li<?php $job->expiresSoon() and print ' class="expires_soon"' ?>>
+ <?php if ($job->isExpired()): ?>
+ Expired
+ <?php else: ?>
+ Expires in <strong><?php echo $job->getDaysBeforeExpires()
?></strong> days
+ <?php endif; ?>
+
+ <?php if ($job->expiresSoon()): ?>
+ - <a href="">Extend</a> for another <?php echo
sfConfig::get('app_active_days') ?> days
+ <?php endif; ?>
+ </li>
+ <?php else: ?>
+ <li>
+ [Bookmark this <?php echo link_to('URL', 'job_show', $job, true)
?> to manage this job in the future.]
+ </li>
+ <?php endif; ?>
+ </ul>
+ </div>
+
+Il y a beaucoup de code, mais la plupart du code est simple à comprendre.
+
+Pour rendre le Template plus lisible, nous avons ajouté un ensemble de
raccourci
+de méthodes dans la classe `JobeetJob` :
+
+ [php]
+<propel>
+ // lib/model/JobeetJob.php
+</propel>
+<doctrine>
+ // lib/model/doctrine/JobeetJob.class.php
+</doctrine>
+ public function getTypeName()
+ {
+<propel>
+ return $this->getType() ? JobeetJobPeer::$types[$this->getType()] : '';
+</propel>
+<doctrine>
+ $types = Doctrine::getTable('JobeetJob')->getTypes();
+ return $this->getType() ? $types[$this->getType()] : '';
+</doctrine>
+ }
+
+ public function isExpired()
+ {
+ return $this->getDaysBeforeExpires() < 0;
+ }
+
+ public function expiresSoon()
+ {
+ return $this->getDaysBeforeExpires() < 5;
+ }
+
+ public function getDaysBeforeExpires()
+ {
+<propel>
+ return floor(($this->getExpiresAt('U') - time()) / 86400);
+</propel>
+<doctrine>
+ return floor((strtotime($this->getExpiresAt()) - time()) / 86400);
+</doctrine>
+ }
+
+a barre d'administrateur affiche les différentes actions en fonction de l'état
de l'emploi :
+
+
+
+
+
+>**NOTE**
+>Vous serez capable de voir la barre "activée" après la section suivante.
+
+Activation et Publication d'un emploi
+-------------------------------------
+
+Dans la section précédente, il y a un lien pour publier l'emploi. Le lien doit
être
+changé pour pointer vers une nouvelle action `publish`. Au lieu de créer une
nouvelle
+~route|Route~, on peut juste configurer la route `job` existante :
+
+ [yml]
+ # apps/frontend/config/routing.yml
+ job:
+ class: sfPropelRouteCollection
+ options:
+ model: JobeetJob
+ column: token
+ object_actions: { publish: put }
+ requirements:
+ token: \w+
+
+Le `object_actions` prend un tableau des actions supplélentaires pour un objet
+donné. Nous pouvons maintenant modifier le lien du lien "Publish" :
+
+ [php]
+ <!-- apps/frontend/modules/job/templates/_admin.php -->
+ <li>
+ <?php echo link_to('Publish', 'job_publish', $job, array('method' =>
'put')) ?>
+ </li>
+
+La dernière étape est de créer l'action `publish` :
+
+ [php]
+ // apps/frontend/modules/job/actions/actions.class.php
+ public function executePublish(sfWebRequest $request)
+ {
+ $request->checkCSRFProtection();
+
+ $job = $this->getRoute()->getObject();
+ $job->publish();
+
+ $this->getUser()->setFlash('notice', sprintf('Your job is now online for
%s days.', sfConfig::get('app_active_days')));
+
+ $this->redirect($this->generateUrl('job_show_user', $job));
+ }
+
+Le lecteur attentif aura remarqué que le lien "Publish" est soumis avec la
+méthode HTTP put. Pour simuler la méthode put, le lien est automatiquement
+converti en un formulaire lorsque vous cliquez dessus.
+
+Et comme nous avons activé la protection ~CSRF~, le helper `link_to()`
embarque
+un jeton CSRF dans le lien et la méthode `checkCSRFProtection()` de l'objet de
la
+requête vérifie la validité de celui-ci lors de la soumission.
+
+La méthode `executePublish()` utilise une nouvelle méthode `publish()` qui peut
+être définie comme suit :
+
+ [php]
+<propel>
+ // lib/model/JobeetJob.php
+</propel>
+<doctrine>
+ // lib/model/doctrine/JobeetJob.class.php
+</doctrine>
+ public function publish()
+ {
+ $this->setIsActivated(true);
+ $this->save();
+ }
+
+Vous pouvez maintenant tester la nouvelle fonctionnalité de publication dans
votre navigateur.
+
+<propel>
+Mais nous avons encore quelque chose à corriger. Les emplois non-activés ne
doivent pas
+être accessibles. Ce qui signifie qu'ils ne doivent pas apparaître sur la page
d'accueil Jobeet
+et ne doivent pas être accessibles par leur URL. Comme nous avons créé une
méthode
+`addActiveJobsCriteria()` pour limiter une `Criteria` aux emplois actifs, nous
pouvons
+le modifier et ajouter des nouvelles exigences à la fin :
+</propel>
+<doctrine>
+Mais nous avons encore quelque chose à corriger. Les emplois non-activés ne
doivent pas
+être accessibles. Ce qui signifie qu'ils ne doivent pas apparaître sur la page
d'accueil Jobeet
+et ne doivent pas être accessibles par leur URL. Comme nous avons créé une
méthode
+`addActiveJobsQuery()` pour limiter une `Doctrine_Query` aux emplois actifs,
nous pouvons
+le modifier et ajouter des nouvelles exigences à la fin :
+</doctrine>
+
+<propel>
+ [php]
+ // lib/model/JobeetJobPeer.php
+ static public function addActiveJobsCriteria(Criteria $criteria = null)
+ {
+ // ...
+
+ $criteria->add(self::IS_ACTIVATED, true);
+
+ return $criteria;
+ }
+</propel>
+<doctrine>
+ [php]
+ // lib/model/doctrine/JobeetJobTable.class.php
+ public function addActiveJobsQuery(Doctrine_Query $q = null)
+ {
+ // ...
+
+ $q->andWhere($alias . '.is_activated = ?', 1);
+
+ return $q;
+ }
+</doctrine>
+
+C'est tout. Vous pouvez maintenant le tester dans votre navigateur. Tous les
emplois
+non-activés ont disparu de la page d'accueil, même si vous connaissez son URL,
ils ne sont
+plus accessibles. Ils sont cependant accessibles si l'on connait URL du jeton
de l'emploi.
+Dans ce cas, l'aperçu de l'emploi sera affichée avec la barre d'administration.
+
+C'est un des grands avantages du modèle MVC et la refactorisation nous a fait
faire
+du chemin. Un seul changement dans une seule méthode a été nécessaire pour
+ajouter la nouvelle exigence.
+
+>**NOTE**
+<propel>
+>Lorsque nous avons créé la méthode `getWithJobs()`, nous avons oublié
d'utiliser
+>la méthode `addActiveJobsCriteria()`. Donc, nous avons besoin de la modifier
et ajouter
+>la nouvelle exigence :
+</propel>
+<doctrine>
+>Lorsque nous avons créé la méthode `getWithJobs()`, nous avons oublié
d'utiliser
+>la méthode `addActiveJobsQuery()`. Donc, nous avons besoin de la modifier et
ajouter
+>la nouvelle exigence :
+</doctrine>
+>
+<propel>
+> [php]
+> class JobeetCategoryPeer extends BaseJobeetCategoryPeer
+> {
+> static public function getWithJobs()
+> {
+> // ...
+>
+> $criteria->add(JobeetJobPeer::IS_ACTIVATED, true);
+>
+> return self::doSelect($criteria);
+> }
+</propel>
+<doctrine>
+> [php]
+> class JobeetCategoryTable extends Doctrine_Table
+> {
+> public function getWithJobs()
+> {
+> // ...
+>
+> $q->andWhere('j.is_activated = ?', 1);
+>
+> return $q->execute();
+> }
+</doctrine>
+
+À demain
+--------
+
+Le tutoriel d'aujourd'hui a été composé avec beaucoup de nouvelles
informations, mais nous
+espérons que vous avez maintenant une meilleure compréhension du framework de
formulaire desymfony.
+
+Je sais que certains d'entre vous ont remarqué que nous avons oublié quelque
chose aujourd'hui ...
+Nous n'avons pas mis en œuvre de test pour les nouvelles fonctionnalités.
Parce que l'écriture de
+test est une partie importante du développement d'une application, c'est la
première chose que nous
+ferons demain.
+
+__ORM__
\ No newline at end of file
--
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.