Author: forresst
Date: 2010-01-25 18:10:32 +0100 (Mon, 25 Jan 2010)
New Revision: 27158

Added:
   doc/branches/1.2/jobeet/fr/16.txt
Log:
[doc-fr][1.2] Add doc in french, jobeet/16 rev:en/24138

Added: doc/branches/1.2/jobeet/fr/16.txt
===================================================================
--- doc/branches/1.2/jobeet/fr/16.txt                           (rev 0)
+++ doc/branches/1.2/jobeet/fr/16.txt   2010-01-25 17:10:32 UTC (rev 27158)
@@ -0,0 +1,1119 @@
+Jour 16 : Services Web
+======================
+
+<doctrine>
+Avant de commencer
+------------------
+
+Dans le jour six quand nous avons défini la route pour l'affichage des emplois 
nommés `job_show_user`
+dans `apps/frontend/config/routing.yml`, nous avons eu une petite erreur dans 
la définition.
+La route doit être la suivante :
+
+    [yml]
+    job_show_user:
+      url:     /job/:company_slug/:location_slug/:id/:position_slug
+      class:   sfDoctrineRoute
+      options:
+        model: JobeetJob
+        type:  object
+        method_for_query: retrieveActiveJob
+      param:   { module: job, action: show }
+      requirements:
+        id: \d+
+
+Notez le changement de l'utilisation de `method` par `method_for_query`. Il 
s'agissait d'un
+petit bogue dans symfony et une erreur dans le tutoriel, vous aurez donc 
besoin de mettre à
+niveau votre projet.
+
+Nous devons aussi apporter une petite modification au schéma `JobAffiliate` 
pour inclure les
+relations plusieurs-vers-plusieurs à `JobeetCategory` pour compléter cette 
journée. Vous pouvez
+voir le schéma complet en regardant le troisième jour, ou juste en regardant 
ce qui est nécessaire
+d'ajouter ci-dessous.
+
+    [yml]
+    JobeetAffiliate:
+      # ...
+      relations:
+        JobeetCategories:
+          class: JobeetCategory
+          refClass: JobeetCategoryAffiliate
+          local: affiliate_id
+          foreign: category_id
+          foreignAlias: JobeetAffiliates
+
+Assurez-vous de reconstruire votre modèle après avoir effectué le changement :
+
+    $ php symfony doctrine:build-model
+</doctrine>
+
+Avec l'ajout des flux sur Jobeet, les demandeurs d'emploi peuvent désormais 
être
+informé sur les nouveaux emplois en temps réel.
+
+De l'autre côté de la barrière, lorsque vous postez un emploi, vous voudrez 
avoir la
+plus grande exposition possible. Si votre emploi est diffusé sur beaucoup de 
petits sites
+Web, vous aurez une meilleure chance de trouver la bonne personne. C'est la 
puissance de la
+[~longue traîne|Longue 
traîne~](http://fr.wikipedia.org/wiki/Longue_tra%C3%AEne).
+Les affiliés seront en mesure de publier plus tard les offres d'emploi sur 
leurs sites
+Web grâce aux services web que nous allons développer aujourd'hui.
+
+Les ~Affiliés~
+--------------
+
+Les exigences décrites lors du jour 2 :
+
+  "Histoires F7 : Un affilié récupère la liste actuelle des emplois actifs"
+
+### Les jeux de test
+
+Créons un nouveau fichier ~fixture|Fixtures~ pour les affiliés :
+
+    [yml]
+<propel>
+    # data/fixtures/030_affiliates.yml
+</propel>
+<doctrine>
+    # data/fixtures/affiliates.yml
+</doctrine>
+    JobeetAffiliate:
+      sensio_labs:
+        url:       http://www.sensio-labs.com/
+        email:     [email protected]
+        is_active: true
+        token:     sensio_labs
+<propel>
+        jobeet_category_affiliates: [programming]
+</propel>
+<doctrine>
+        JobeetCategories: [programming]
+</doctrine>
+
+      symfony:
+        url:       http://www.symfony-project.org/
+        email:     [email protected]
+        is_active: false
+        token:     symfony
+<propel>
+        jobeet_category_affiliates: [design, programming]
+</propel>
+<doctrine>
+        JobeetCategories: [design, programming]
+</doctrine>
+
+<propel>
+La création d'enregistrements pour une table moyenne des relations 
plusieurs-vers-plusieurs
+est aussi simple que la définition d'un tableau avec une clé du nom de table, 
plus un `s`.
+</propel>
+<doctrine>
+La création d'enregistrements des relations plusieurs-vers-plusieurs est aussi 
simple que
+la définition d'un tableau avec la clé qui est le nom de la relation.
+</doctrine>
+Le contenu du tableau est les noms des objets tels qu'ils sont définis dans 
les fichiers
+fixture. Vous pouvez lier des objets à partir de fichiers différents, mais les 
noms doivent
+être définies au préalable.
+
+Dans le fichier fixture, les jetons sont codés en dur pour simplifier le test,
+mais quand un utilisateur réel demande un compte, le ~jeton|Jeton~ devra être
+généré :
+
+<propel>
+    [php]
+    // lib/model/JobeetAffiliate.php
+    class JobeetAffiliate extends BaseJobeetAffiliate
+    {
+      public function save(PropelPDO $con = null)
+      {
+        if (!$this->getToken())
+        {
+          $this->setToken(sha1($this->getEmail().rand(11111, 99999)));
+        }
+
+        return parent::save($con);
+      }
+
+      // ...
+    }
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetAffiliate.php
+    class JobeetAffiliate extends BaseJobeetAffiliate
+    {
+      public function preValidate($event)
+      {
+        $object = $event->getInvoker();
+
+        if (!$object->getToken())
+        {
+          $object->setToken(sha1($object->getEmail().rand(11111, 99999)));
+        }
+      }
+
+      // ...
+    }
+</doctrine>
+
+Vous pouvez désormais recharger les données :
+
+    $ php symfony propel:data-load
+
+### Le web service emploi
+
+Comme toujours, lorsque vous créez une nouvelle ressource, c'est une bonne 
habitude de définir la première adresse ~URL~ :
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    api_jobs:
+      url:     /api/:token/jobs.:sf_format
+      class:   sfPropelRoute
+      param:   { module: api, action: list }
+      options: { model: JobeetJob, type: list, method: getForToken }
+      requirements:
+        sf_format: (?:xml|json|yaml)
+
+Pour cette route, la variable spéciale ~`sf_format`~ termine l'URL et les 
valeurs
+valides sont `xml`, `json` ou `yaml`.
+
+La méthode `getForToken()` est appelée lorsque l'action récupère la collection
+d'objets liés à la route. Comme nous avons besoin de vérifier que l'affilié est
+activé, nous avons besoin de modifier le comportement par défaut de la route :
+
+<propel>
+    [php]
+    // lib/model/JobeetJobPeer.php
+    class JobeetJobPeer extends BaseJobeetJobPeer
+    {
+      static public function getForToken(array $parameters)
+      {
+        $affiliate = JobeetAffiliatePeer::getByToken($parameters['token']);
+        if (!$affiliate || !$affiliate->getIsActive())
+        {
+          throw new sfError404Exception(sprintf('Affiliate with token "%s" 
does not exist or is not activated.', $parameters['token']));
+        }
+
+        return $affiliate->getActiveJobs();
+      }
+
+      // ...
+    }
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetJobTable.class.php
+    class JobeetJobTable extends Doctrine_Table
+    {
+      public function getForToken(array $parameters)
+      {
+        $affiliate = Doctrine::getTable('JobeetAffiliate')
+          ➥ ->findOneByToken($parameters['token']);
+        if (!$affiliate || !$affiliate->getIsActive())
+        {
+          throw new sfError404Exception(sprintf('Affiliate with token "%s" 
does not exist or is not activated.', $parameters['token']));
+        }
+
+        return $affiliate->getActiveJobs();
+      }
+
+      // ...
+    }
+</doctrine>
+
+Si le jeton n'existe pas dans la base de données, nous lançons une exception
+`sfError404Exception`. Cette classe d'exception est ensuite automatiquement 
converti en
+une réponse `~404|Erreur 404~`. C'est le moyen le plus simple pour générer une 
page `404`
+d'une classe de modèle.
+
+<propel>
+La méthode `getForToken()` utilise deux nouvelles méthodes que nous allons 
créer.
+
+Premièrement, la méthode `getByToken()` doit être créé pour obtenir un affilié 
donné
+par son jeton :
+
+    [php]
+    // lib/model/JobeetAffiliatePeer.php
+    class JobeetAffiliatePeer extends BaseJobeetAffiliatePeer
+    {
+      static public function getByToken($token)
+      {
+        $criteria = new Criteria();
+        $criteria->add(self::TOKEN, $token);
+
+        return self::doSelectOne($criteria);
+      }
+    }
+
+Ensuite, la méthode `getActiveJobs()` retourne la liste des emplois 
actuellement actif
+pour les catégories sélectionnées par l'affilié :
+
+</propel>
+<doctrine>
+La méthode `getForToken()` utilise une nouvelle méthode nommée 
`getActiveJobs()` et
+renvoie la liste des emplois actuellement actifs :
+</doctrine>
+
+<propel>
+    [php]
+    // lib/model/JobeetAffiliate.php
+    class JobeetAffiliate extends BaseJobeetAffiliate
+    {
+      public function getActiveJobs()
+      {
+        $cas = $this->getJobeetCategoryAffiliates();
+        $categories = array();
+        foreach ($cas as $ca)
+        {
+          $categories[] = $ca->getCategoryId();
+        }
+
+        $criteria = new Criteria();
+        $criteria->add(JobeetJobPeer::CATEGORY_ID, $categories, Criteria::IN);
+        JobeetJobPeer::addActiveJobsCriteria($criteria);
+
+        return JobeetJobPeer::doSelect($criteria);
+      }
+
+      // ...
+    }
+</propel>
+<doctrine>
+    [php]
+    // lib/model/doctrine/JobeetAffiliate.class.php
+    class JobeetAffiliate extends BaseJobeetAffiliate
+    {
+      public function getActiveJobs()
+      {
+        $q = Doctrine_Query::create()
+          ->select('j.*')
+          ->from('JobeetJob j')
+          ->leftJoin('j.JobeetCategory c')
+          ->leftJoin('c.JobeetAffiliates a')
+          ->where('a.id = ?', $this->getId());
+
+        $q = Doctrine::getTable('JobeetJob')->addActiveJobsQuery($q);
+
+        return $q->execute();
+      }
+
+      // ...
+    }
+</doctrine>
+
+La dernière étape consiste à créer l'action `api` et les Templates. Démarrez le
+module avec la tâche `generate:module` :
+
+    $ php symfony generate:module frontend api
+
+>**NOTE**
+>Comme nous n'utiliserons pas l'action par défaut `index`, vous pouvez la
+>supprimer de la classe action, et retirez le Template associé 
`indexSucess.php`.
+
+### L'action
+
+Tous les formats partagent la même action `list` :
+
+    [php]
+    // apps/frontend/modules/api/actions/actions.class.php
+    public function executeList(sfWebRequest $request)
+    {
+      $this->jobs = array();
+      foreach ($this->getRoute()->getObjects() as $job)
+      {
+        $this->jobs[$this->generateUrl('job_show_user', $job, true)] =
+         ➥ $job->asArray($request->getHost());
+      }
+    }
+
+Plutôt que de transmettre un tableau d'objets `JobeetJob` aux Templates, on 
passe
+un tableau de chaînes. Comme nous avons trois Templates différents pour la 
même action,
+la logique pour traiter les valeurs a été refactoriser dans la méthode
+`JobeetJob::asArray()` :
+
+    [php]
+<propel>
+    // lib/model/JobeetJob.php
+</propel>
+<doctrine>
+    // lib/model/doctrine/JobeetJob.class.php
+</doctrine>
+    class JobeetJob extends BaseJobeetJob
+    {
+      public function asArray($host)
+      {
+        return array(
+          'category'     => $this->getJobeetCategory()->getName(),
+          'type'         => $this->getType(),
+          'company'      => $this->getCompany(),
+          'logo'         => $this->getLogo() ? 
'http://'.$host.'/uploads/jobs/'.$this->getLogo() : null,
+          'url'          => $this->getUrl(),
+          'position'     => $this->getPosition(),
+          'location'     => $this->getLocation(),
+          'description'  => $this->getDescription(),
+          'how_to_apply' => $this->getHowToApply(),
+<propel>
+          'expires_at'   => $this->getCreatedAt('c'),
+</propel>
+<doctrine>
+          'expires_at'   => $this->getCreatedAt(),
+</doctrine>
+        );
+      }
+
+      // ...
+    }
+
+### Le format `xml`
+
+Le support du format `xml` est aussi simple que la création d'un Template :
+
+    [php]
+    <!-- apps/frontend/modules/api/templates/listSuccess.xml.php -->
+    <?xml version="1.0" encoding="utf-8"?>
+    <jobs>
+    <?php foreach ($jobs as $url => $job): ?>
+      <job url="<?php echo $url ?>">
+    <?php foreach ($job as $key => $value): ?>
+        <<?php echo $key ?>><?php echo $value ?></<?php echo $key ?>>
+    <?php endforeach; ?>
+      </job>
+    <?php endforeach; ?>
+    </jobs>
+
+### Le format `json`
+
+Le support du [format JSON](http://json.org/) est similaire :
+
+    [php]
+    <!-- apps/frontend/modules/api/templates/listSuccess.json.php -->
+    [
+    <?php $nb = count($jobs); $i = 0; foreach ($jobs as $url => $job): ++$i ?>
+    {
+      "url": "<?php echo $url ?>",
+    <?php $nb1 = count($job); $j = 0; foreach ($job as $key => $value): ++$j ?>
+      "<?php echo $key ?>": <?php echo json_encode($value).($nb1 == $j ? '' : 
',') ?>
+
+    <?php endforeach; ?>
+    }<?php echo $nb == $i ? '' : ',' ?>
+
+    <?php endforeach; ?>
+    ]
+
+### ~Le format `yaml`|Formats (Création)~
+
+Pour les formats intégrés,  symfony fait une certaine configuration en 
arrière-plan, comme
+changer le type de contenu ou la désactivation de la mise en page.
+
+Comme le format YAML n'est pas dans la liste des formats prédéfinis d'une 
requête, le
+type de contenu de la réponse peut être modifié et la mise en page désactivée 
dans l'action :
+
+    [php]
+    class apiActions extends sfActions
+    {
+      public function executeList(sfWebRequest $request)
+      {
+        $this->jobs = array();
+        foreach ($this->getRoute()->getObjects() as $job)
+        {
+          $this->jobs[$this->generateUrl('job_show_user', $job, true)] =
+           ➥ $job->asArray($request->getHost());
+        }
+
+        switch ($request->getRequestFormat())
+        {
+          case 'yaml':
+            $this->setLayout(false);
+            $this->getResponse()->setContentType('text/yaml');
+            break;
+        }
+      }
+    }
+
+Dans une action, la méthode `setLayout()` modifie le ~layout|Layout 
(Désactivation)~ par défaut
+ou le désactive lorsqu'il est à `false`.
+
+Le Template pour YAML se lit comme suit :
+
+    [php]
+    <!-- apps/frontend/modules/api/templates/listSuccess.yaml.php -->
+    <?php foreach ($jobs as $url => $job): ?>
+    -
+      url: <?php echo $url ?>
+
+    <?php foreach ($job as $key => $value): ?>
+      <?php echo $key ?>: <?php echo sfYaml::dump($value) ?>
+
+    <?php endforeach; ?>
+    <?php endforeach; ?>
+
+Si vous essayez d'appeler le service web avec un jeton non-valide, vous aurez 
une page
+XML 404 pour le format XML, et une page JSON 404 pour le format JSON. Mais 
pour le format
+YAML, symfony ne sait pas quoi rendre.
+
+Lorsque vous créez un format, une ~erreur de Template personnalisée|Erreur de 
Template personnalisée~
+doit être créé. Le Template sera utilisé pour les pages 404 et pour toutes les 
autres exceptions.
+
+Comme l'~exception|Gestion de l'exception~ devrait être différente dans 
l'environnement
+de production et de développement, deux fichiers sont nécessaires 
(`config/error/exception.yaml.php`
+pour le débogage et `config/error/error.yaml.php` pour la production) :
+
+    [php]
+    // config/error/exception.yaml.php
+    <?php echo sfYaml::dump(array(
+      'error'       => array(
+        'code'      => $code,
+        'message'   => $message,
+        'debug'     => array(
+          'name'    => $name,
+          'message' => $message,
+          'traces'  => $traces,
+        ),
+    )), 4) ?>
+
+    // config/error/error.yaml.php
+    <?php echo sfYaml::dump(array(
+      'error'       => array(
+        'code'      => $code,
+        'message'   => $message,
+    ))) ?>
+
+Avant de l'essayer, vous devez créer un layout pour le format YAML :
+
+    [php]
+    // apps/frontend/templates/layout.yaml.php
+    <?php echo $sf_content ?>
+
+![404](http://www.symfony-project.org/images/jobeet/1_2/16/404.png)
+
+>**TIP**
+>La redéfinition de l'erreur 404 et l'~exception|Gestion de l'exception~ des 
Templates
+>pour des Templates intégrés est aussi simple que de créer un fichier dans le 
répertoire
+>`config/error/`.
+
+~Les Tests du service web|Tests (Services web)~
+-----------------------------------------------
+
+Pour tester le service web, copiez les fixtures de l'affilié de 
`data/fixtures/` vers
+le répertoire `test/fixtures/` et remplacez le contenu auto-généré du fichier
+`apiActionsTest.php` avec le contenu suivant :
+
+    [php]
+    // test/functional/frontend/apiActionsTest.php
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
+
+    $browser = new JobeetTestFunctional(new sfBrowser());
+    $browser->loadData();
+
+    $browser->
+      info('1 - Web service security')->
+
+      info('  1.1 - A token is needed to access the service')->
+      get('/api/foo/jobs.xml')->
+      with('response')->isStatusCode(404)->
+
+      info('  1.2 - An inactive account cannot access the web service')->
+      get('/api/symfony/jobs.xml')->
+      with('response')->isStatusCode(404)->
+
+      info('2 - The jobs returned are limited to the categories configured for 
the affiliate')->
+      get('/api/sensio_labs/jobs.xml')->
+      with('request')->isFormat('xml')->
+      with('response')->checkElement('job', 32)->
+
+      info('3 - The web service supports the JSON format')->
+      get('/api/sensio_labs/jobs.json')->
+      with('request')->isFormat('json')->
+      with('response')->contains('"category": "Programming"')->
+
+      info('4 - The web service supports the YAML format')->
+      get('/api/sensio_labs/jobs.yaml')->
+      with('response')->begin()->
+        isHeader('content-type', 'text/yaml; charset=utf-8')->
+        contains('category: Programming')->
+      end()
+    ;
+
+Dans ce test, vous remarquerez deux nouvelles méthodes :
+
+  * `isFormat()` : Elle teste le format d'une requête
+  * `contains()` : Pour le format non-HTML, elle contrôle que la réponse 
contient
+                   l'extrait du texte
+
+Le formulaire de demande d'affiliation
+--------------------------------------
+
+Maintenant que le service web est prêt à être utilisé, nous allons créer le
+formulaire de création de compte pour les affiliés. Nous allons décrire une 
fois
+encore la procédure classique de l'ajout d'une nouvelle fonctionnalité à une 
application.
+
+### Routage
+
+Vous le deviner. La route est la première chose que nous créons :
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    affiliate:
+      class:   sfPropelRouteCollection
+      options:
+        model: JobeetAffiliate
+        actions: [new, create]
+        object_actions: { wait: get }
+
+C'est une collection de route classique de ##ORM## avec une nouvelle option de
+configuration : `actions`. Comme nous n'avons pas besoin des sept actions par 
défaut
+définies par la route, l'option `actions` se charge de la route seulement pour 
les actions
+`new` et `create`. La route supplémentaire `wait` sera utilisée pour donner 
certaines informations
+au sujet de son compte.
+
+### Démarrage
+
+La deuxième étape classique est de générer un module :
+
+    $ php symfony propel:generate-module frontend affiliate JobeetAffiliate 
--non-verbose-templates
+
+### Les Templates
+
+La tâche `propel:generate-module` génère les sept actions classiques et leurs
+~Templates|Templates~ correspondants. Dans le répertoire `templates/`, 
supprimer tous
+les fichiers sauf `_form.php` and `newSuccess.php`. Et pour les fichiers que 
nous
+avons gardés, remplacez leur contenu avec le texte suivant:
+
+    [php]
+    <!-- apps/frontend/modules/affiliate/templates/newSuccess.php -->
+    <?php use_stylesheet('job.css') ?>
+
+    <h1>Become an Affiliate</h1>
+
+    <?php include_partial('form', array('form' => $form)) ?>
+
+    <!-- apps/frontend/modules/affiliate/templates/_form.php -->
+    <?php include_stylesheets_for_form($form) ?>
+    <?php include_javascripts_for_form($form) ?>
+
+    <?php echo form_tag_for($form, 'affiliate') ?>
+      <table id="job_form">
+        <tfoot>
+          <tr>
+            <td colspan="2">
+              <input type="submit" value="Submit" />
+            </td>
+          </tr>
+        </tfoot>
+        <tbody>
+          <?php echo $form ?>
+        </tbody>
+      </table>
+    </form>
+
+Créez le Template `waitSuccess.php` :
+
+    [php]
+    <!-- apps/frontend/modules/affiliate/templates/waitSuccess.php -->
+    <h1>Your affiliate account has been created</h1>
+
+    <div style="padding: 20px">
+      Thank you!
+      You will receive an email with your affiliate token
+      as soon as your account will be activated.
+    </div>
+
+Enfin, modifiez le lien dans le pied de page pour qu'il pointe vers le module 
`affiliate` :
+
+    [php]
+    // apps/frontend/templates/layout.php
+    <li class="last">
+      <a href="<?php echo url_for('@affiliate_new') ?>">Become an affiliate</a>
+    </li>
+
+### Les actions
+
+Ici encore, comme nous n'utiliserons que le formulaire de création, ouvrez le
+fichier `actions.class.php` et supprimez toutes les méthodes `executeNew()`,
+`executeCreate()` et `processForm()`.
+
+Pour l'action `processForm()`, modifiez l'URL de redirection pour l'action 
`wait` :
+
+    [php]
+    // apps/frontend/modules/affiliate/actions/actions.class.php
+    $this->redirect($this->generateUrl('affiliate_wait', $jobeet_affiliate));
+
+L'action `wait` est simple car nous n'avons pas besoin de passer quelque chose 
pour le Template :
+
+    [php]
+    // apps/frontend/modules/affiliate/actions/actions.class.php
+    public function executeWait()
+    {
+    }
+
+L'affilié ne peut pas choisir son jeton, ni activer son compte sans tarder.
+Ouvrez le fichier `JobeetAffiliateForm` pour personnaliser le 
~formulaire|Formulaires~ :
+
+    [php]
+<propel>
+    // lib/form/JobeetAffiliateForm.class.php
+</propel>
+<doctrine>
+    // lib/form/doctrine/JobeetAffiliateForm.class.php
+</doctrine>
+    class JobeetAffiliateForm extends BaseJobeetAffiliateForm
+    {
+      public function configure()
+      {
+<propel>
+        unset($this['is_active'], $this['token'], $this['created_at']);
+        
$this->widgetSchema['jobeet_category_affiliate_list']->setOption('expanded', 
true);
+        
$this->widgetSchema['jobeet_category_affiliate_list']->setLabel('Categories');
+
+        
$this->validatorSchema['jobeet_category_affiliate_list']->setOption('required', 
true);
+</propel>
+<doctrine>
+        unset($this['is_active'], $this['token'], $this['created_at'], 
$this['updated_at']);
+
+        $this->widgetSchema['jobeet_categories_list']->setOption('expanded', 
true);
+        $this->widgetSchema['jobeet_categories_list']->setLabel('Categories');
+
+        
$this->validatorSchema['jobeet_categories_list']->setOption('required', true);
+</doctrine>
+
+        $this->widgetSchema['url']->setLabel('Your website URL');
+        $this->widgetSchema['url']->setAttribute('size', 50);
+
+        $this->widgetSchema['email']->setAttribute('size', 50);
+
+        $this->validatorSchema['email'] = new 
sfValidatorEmail(array('required' => true));
+      }
+    }
+
+Le framework de formulaire prend en charge les ~relations 
plusieurs-vers-plusieurs|Relations
+plusieurs-vers-plusieurs (Formulaires)~ comme n'importe quel autre colonne. 
Par défaut, une telle
+relation est rendue comme une liste déroulante grâce au widget 
`sfWidgetFormChoice`. Comme on le
+voit pendant le jour 10, nous avons changé le tag rendu en utilisant l'option 
`expanded`.
+
+Comme les emails et les URL ont tendance à être très supérieure à la taille 
par défaut d'une balise
+input, les attributs HTML par défaut peuvent être définis en utilisant la 
méthode `setAttribute()`.
+
+![Formulaire de 
l'affilié](http://www.symfony-project.org/images/jobeet/1_2/16/affiliate_form.png)
+
+### Les tests
+
+La dernière étape consiste à écrire des ~tests fonctionnels|Formulaires 
(Tests)~ pour la nouvelle fonctionnalité.
+
+Remplacez les tests générés pour le module `affiliate` par le code suivant :
+
+    [php]
+    // test/functional/frontend/affiliateActionsTest.php
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
+
+    $browser = new JobeetTestFunctional(new sfBrowser());
+    $browser->loadData();
+
+    $browser->
+      info('1 - An affiliate can create an account')->
+
+      get('/affiliate/new')->
+      click('Submit', array('jobeet_affiliate' => array(
+        'url'                            => 'http://www.example.com/',
+        'email'                          => '[email protected]',
+<propel>
+        'jobeet_category_affiliate_list' => 
array($browser->getProgrammingCategory()->getId()),
+</propel>
+<doctrine>
+        'jobeet_categories_list'         => 
array(Doctrine::getTable('JobeetCategory')->findOneBySlug('programming')->getId()),
+</doctrine>
+      )))->
+      isRedirected()->
+      followRedirect()->
+      with('response')->checkElement('#content h1', 'Your affiliate account 
has been created')->
+
+      info('2 - An affiliate must at least select one category')->
+
+      get('/affiliate/new')->
+      click('Submit', array('jobeet_affiliate' => array(
+        'url'   => 'http://www.example.com/',
+        'email' => '[email protected]',
+      )))->
+<propel>
+      with('form')->isError('jobeet_category_affiliate_list')
+</propel>
+<doctrine>
+      with('form')->isError('jobeet_categories_list')
+</doctrine>
+    ;
+
+<propel>
+Pour simuler la sélection des cases à cocher, passez un tableau d'identifiants
+à cocher. Pour simplifier la tâche, une nouvelle méthode 
`getProgrammingCategory()`
+a été créé dans la classe `JobeetTestFunctional` :
+
+    [php]
+    // lib/test/JobeetTestFunctional.class.php
+    class JobeetTestFunctional extends sfTestFunctional
+    {
+      public function getProgrammingCategory()
+      {
+        $criteria = new Criteria();
+        $criteria->add(JobeetCategoryPeer::SLUG, 'programming');
+
+        return JobeetCategoryPeer::doSelectOne($criteria);
+      }
+
+      // ...
+    }
+
+Mais comme nous avons déjà ce code dans la méthode 
`getMostRecentProgrammingJob()`,
+il est temps de ~refactoriser|Refactorisation~ le code et créer une
+méthode `getForSlug()` dans `JobeetCategoryPeer` :
+
+    [php]
+    // lib/model/JobeetCategoryPeer.php
+    static public function getForSlug($slug)
+    {
+      $criteria = new Criteria();
+      $criteria->add(self::SLUG, $slug);
+
+      return self::doSelectOne($criteria);
+    }
+
+Ensuite, remplacez les deux occurrences de ce code dans `JobeetTestFunctional`.
+</propel>
+
+Le backend des affiliés
+-----------------------
+
+Pour le ~backend|Backend~, un module `affiliate` doit être créé pour activer
+les affiliés par l'administrateur :
+
+    $ php symfony propel:generate-admin backend JobeetAffiliate 
--module=affiliate
+
+Pour accéder au nouveau module créé, ajoutez un lien dans le menu principal 
avec
+le nombre d'affiliés qui ont besoin d'être activés :
+
+    [php]
+    <!-- apps/backend/templates/layout.php -->
+    <li>
+<propel>
+      <a href="<?php echo url_for('@jobeet_affiliate') ?>">
+        Affiliates - <strong><?php echo 
JobeetAffiliatePeer::countToBeActivated() ?></strong>
+      </a>
+</propel>
+<doctrine>
+      <a href="<?php echo url_for('@jobeet_affiliate_affiliate') ?>">
+        Affiliates - <strong><?php echo 
Doctrine::getTable('JobeetAffiliate')->countToBeActivated() ?></strong>
+      </a>
+</doctrine>
+    </li>
+
+<propel>
+    // lib/model/JobeetAffiliatePeer.php
+    class JobeetAffiliatePeer extends BaseJobeetAffiliatePeer
+    {
+      static public function countToBeActivated()
+      {
+        $criteria = new Criteria();
+        $criteria->add(self::IS_ACTIVE, 0);
+
+        return self::doCount($criteria);
+      }
+</propel>
+<doctrine>
+    // lib/model/doctrine/JobeetAffiliateTable.class.php
+    class JobeetAffiliateTable extends Doctrine_Table
+    {
+      public function countToBeActivated()
+      {
+        $q = $this->createQuery('a')
+          ->where('a.is_active = ?', 0);
+
+        return $q->count();
+      }
+</doctrine>
+
+      // ...
+
+    }
+
+Comme la seule action nécessaire dans le backend est d'activer ou de désactiver
+des comptes, changez la section `config` du générateur par défaut pour 
simplifier
+un peu l'interface et ajouter un lien pour activer des comptes directement à 
partir
+de la vue de liste :
+
+    [yml]
+    # apps/backend/modules/affiliate/config/generator.yml
+    config:
+      fields:
+        is_active: { label: Active? }
+      list:
+        title:   Affiliate Management
+        display: [is_active, url, email, token]
+        sort:    [is_active]
+        object_actions:
+          activate:   ~
+          deactivate: ~
+        batch_actions:
+          activate:   ~
+          deactivate: ~
+        actions: {}
+      filter:
+        display: [url, email, is_active]
+
+Pour rendre les administrateurs plus productifs, changez les filtres par 
défaut pour
+ne montrer que les affiliées a activé :
+
+    [php]
+    // 
apps/backend/modules/affiliate/lib/affiliateGeneratorConfiguration.class.php
+    class affiliateGeneratorConfiguration extends 
BaseAffiliateGeneratorConfiguration
+    {
+      public function getFilterDefaults()
+      {
+        return array('is_active' => '0');
+      }
+    }
+
+Le seul autre code à écrire, c'est les actions `activate` et `deactivate` :
+
+    [php]
+    // apps/backend/modules/affiliate/actions/actions.class.php
+    class affiliateActions extends autoAffiliateActions
+    {
+      public function executeListActivate()
+      {
+        $this->getRoute()->getObject()->activate();
+
+<propel>
+        $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+        $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+      }
+
+      public function executeListDeactivate()
+      {
+        $this->getRoute()->getObject()->deactivate();
+
+<propel>
+        $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+        $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+      }
+
+      public function executeBatchActivate(sfWebRequest $request)
+      {
+<propel>
+        $affiliates = 
JobeetAffiliatePeer::retrieveByPks($request->getParameter('ids'));
+</propel>
+<doctrine>
+        $q = Doctrine_Query::create()
+          ->from('JobeetAffiliate a')
+          ->whereIn('a.id', $request->getParameter('ids'));
+
+        $affiliates = $q->execute();
+</doctrine>
+
+        foreach ($affiliates as $affiliate)
+        {
+          $affiliate->activate();
+        }
+
+<propel>
+        $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+        $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+      }
+
+      public function executeBatchDeactivate(sfWebRequest $request)
+      {
+<propel>
+        $affiliates = 
JobeetAffiliatePeer::retrieveByPks($request->getParameter('ids'));
+</propel>
+<doctrine>
+        $q = Doctrine_Query::create()
+          ->from('JobeetAffiliate a')
+          ->whereIn('a.id', $request->getParameter('ids'));
+
+        $affiliates = $q->execute();
+</doctrine>
+
+        foreach ($affiliates as $affiliate)
+        {
+          $affiliate->deactivate();
+        }
+
+<propel>
+        $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+        $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+      }
+    }
+
+<propel>
+    // lib/model/JobeetAffiliate.php
+</propel>
+<doctrine>
+    // lib/model/doctrine/JobeetAffiliate.class.php
+</doctrine>
+    class JobeetAffiliate extends BaseJobeetAffiliate
+    {
+      public function activate()
+      {
+        $this->setIsActive(true);
+
+        return $this->save();
+      }
+
+      public function deactivate()
+      {
+        $this->setIsActive(false);
+
+        return $this->save();
+      }
+
+      // ...
+    }
+
+![Le backend des 
affiliés](http://www.symfony-project.org/images/jobeet/1_2/16/backend.png)
+
+Envoi d'~Emails~
+----------------
+
+Chaque fois qu'un compte affilié est activé par l'administrateur, un e-mail
+doit être envoyé à la filiale pour valider son inscription et lui donner son
+jeton.
+
+PHP dispose d'un certain nombre de bonne bibliothèque pour envoyer des emails 
comme
+[SwiftMailer](http://www.swiftmailer.org/),
+[Zend_Mail](http://framework.zend.com/) et
+[ezcMail](http://ezcomponents.org/docs/tutorials/Mail). Comme nous allons 
utiliser
+d'autres bibliothèques du ~Framework Zend~ dans un jour à venir, nous allons 
utiliser
+`~Zend_Mail~` pour envoyer nos emails.
+
+### Installation et configuration du Framework Zend
+
+La ~bibliothèque|Bibliothèques tierces~ Zend Mail fait partie du framework 
Zend. Comme nous
+n'aurons pas besoin de l'ensemble du framework Zend, nous allons installer 
uniquement les parties
+nécessaires dans le répertoire `lib/vendor/`, à côté du framework symfony.
+
+D'abord, téléchargez le
+[framework Zend](http://framework.zend.com/download/overview) et décompressez
+les fichiers afin d'avoir le répertoire `lib/vendor/Zend/`.
+
+>**NOTE**
+>Les explications suivantes ont été testé avec la version 1.8.0 du
+>framework Zend.
+
+Vous pouvez nettoyer le répertoire en enlevant tout sauf les fichiers et les
+répertoires suivants :
+
+  * `Exception.php`
+  * `Loader/`
+  * `Loader.php`
+  * `Mail/`
+  * `Mail.php`
+  * `Mime/`
+  * `Mime.php`
+  * `Search/`
+
+>**NOTE**
+>Le répertoire `Search/` n'est pas nécessaire pour l'envoi d'e-mail, mais sera
+>nécessaire pour le tutoriel demain.
+
+Puis, ajoutez le code suivant à la classe `ProjectConfiguration` pour fournir 
un
+moyen simple d'enregistrer le chargement automatique de Zend :
+
+    [php]
+    // config/ProjectConfiguration.class.php
+    class ProjectConfiguration extends sfProjectConfiguration
+    {
+      static protected $zendLoaded = false;
+
+      static public function registerZend()
+      {
+        if (self::$zendLoaded)
+        {
+          return;
+        }
+
+        
set_include_path(sfConfig::get('sf_lib_dir').'/vendor'.PATH_SEPARATOR.get_include_path());
+        require_once sfConfig::get('sf_lib_dir').'/vendor/Zend/Autoloader.php';
+        Zend_Loader_Autoloader::getInstance();
+        self::$zendLoaded = true;
+      }
+
+      // ...
+    }
+
+### Envoi d'emails
+
+Modifiez l'action `activate` pour envoyer un email lorsque l'administrateur 
valide
+un affilié :
+
+    [php]
+    // apps/backend/modules/affiliate/actions/actions.class.php
+    class affiliateActions extends autoAffiliateActions
+    {
+      public function executeListActivate()
+      {
+        $affiliate = $this->getRoute()->getObject();
+        $affiliate->activate();
+
+        // send an email to the affiliate
+        ProjectConfiguration::registerZend();
+        $mail = new Zend_Mail();
+        $mail->setBodyText(<<<EOF
+    Your Jobeet affiliate account has been activated.
+
+    Your token is {$affiliate->getToken()}.
+
+    The Jobeet Bot.
+    EOF
+    );
+        $mail->setFrom('[email protected]', 'Jobeet Bot');
+        $mail->addTo($affiliate->getEmail());
+        $mail->setSubject('Jobeet affiliate token');
+        $mail->send();
+
+<propel>
+        $this->redirect('@jobeet_affiliate');
+</propel>
+<doctrine>
+        $this->redirect('@jobeet_affiliate_affiliate');
+</doctrine>
+      }
+
+      // ...
+    }
+
+Pour que le code fonctionne, vous devez changer `[email protected]` en une 
adresse
+email réelle.
+
+>**NOTE**
+>Un tutoriel complet sur la bibliothèque `Zend_Mail` est disponible sur
+>le [site du framework 
Zend](http://framework.zend.com/manual/en/zend.mail.html).
+
+À demain
+--------
+
+Merci à l'architecture ~REST~ de symfony, il est assez facile à mettre en œuvre
+des services web pour vos projets. Mais, nous avons écrit le code pour un 
service web
+seulement en lecture aujourd'hui, vous avez assez de connaissances symfony 
pour mettre
+en œuvre un service web de lecture-écriture.
+
+La mise en œuvre du formulaire de création du compte affilié dans le frontend 
et
+son homologue dans le backend était vraiment facile, car vous vous êtes 
maintenant
+familiarisé avec le processus d'ajout de nouvelles fonctionnalités à votre 
projet.
+
+Si vous vous rappelez les exigences du jour 2 :
+
+  "L'affilié peut également limiter le nombre d'emplois qui seront retourné et 
affiner
+  sa requête en spécifiant une catégorie."
+  
+La mise en œuvre de cette fonctionnalité est si facile que nous allons vous
+permettre de le faire ce soir.
+
+Demain, nous allons mettre en œuvre la dernière fonctionnalité manquante du 
site Jobeet,
+le moteur de recherche.
+
+__ORM__

-- 
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.

Reply via email to