Author: forresst Date: 2010-01-20 13:36:25 +0100 (Wed, 20 Jan 2010) New Revision: 26933
Added: doc/branches/1.2/jobeet/fr/12.txt Log: [doc-fr][1.2] Add doc in french, jobeet/12 rev:en/20684 Added: doc/branches/1.2/jobeet/fr/12.txt =================================================================== --- doc/branches/1.2/jobeet/fr/12.txt (rev 0) +++ doc/branches/1.2/jobeet/fr/12.txt 2010-01-20 12:36:25 UTC (rev 26933) @@ -0,0 +1,1260 @@ +Jour 12 : L'Admin Generator +=========================== + +Avec les ajouts que nous avons faits hier sur Jobeet, l'application frontend est maintenant +pleinement utilisable par les chercheurs d'emploi et les posteurs d'emplois. Il est temps de +parler un peu de l'application backend. + +Aujourd'hui, grâce à la fonctionnalité de l'~admin generator|Admin Generator~ de symfony, +nous allons développer une interface backend complète pour Jobeet en seulement une heure. + +La création du backend +---------------------- + +La toute première étape consiste à créer l'application ~backend|Backend~. Si vous +vous souvenez bien, vous devriez vous rappeler comment le faire avec la tâche +`generate:app` : + + $ php symfony generate:app --escaping-strategy=on + ➥ --csrf-secret=UniqueSecret1 backend + +Même si l'application backend ne sera utilisée que par les administrateurs +de Jobeet, nous avons activé toutes les fonctions intégrées de sécurité de symfony. + +>**TIP** +>Si vous souhaitez utiliser des caractères spéciaux dans le mot de passe, comme un signe +>dollar (`$`) par exemple, vous avez besoin de l'échapper correctement sur la ligne de commande : +> +> $ php symfony generate:app --csrf-secret=Unique\$ecret backend + +L'application backend est maintenant disponible à l'adresse +`http://jobeet.localhost/backend.php/` pour l'environnement de `prod` et +`http://jobeet.localhost/backend_dev.php/` pour l'environnement de `dev`. + +>**NOTE** +>Lorsque vous avez créé l'application frontend, le contrôleur frontal de la production a été +>nommé `index.php`. Comme vous ne pouvez avoir qu'un seul fichier `index.php` par répertoire, +>symfony crée un fichier `index.php` pour le premier contrôleur de la production et les noms des +>autres après le nom d'application. + +Si vous essayez de recharger les données de tests avec la tâche `propel:data-load`, elle +ne fonctionnera plus. C'est parce que la méthode `JobeetJob::save()` a besoin d'accéder au +fichier de configuration ~`app.yml`~ depuis l'application frontend. Comme nous avons maintenant +deux applications, symfony utilise le premier qu'il trouve, qui est maintenant le +`backend`. + +Mais comme on le voit pendant le jour 8, les paramètres peuvent être configurés à différents +niveaux. En déplaçant le contenu du fichier `apps/frontend/config/app.yml` vers +`config/app.yml`, les paramètres seront partagés par toutes les applications et +le problème sera réglé. Changez le maintenant car nous allons utiliser les classes du +modèle assez largement dans l'admin generator, et donc nous aurons besoin des variables +définies dans `app.yml` dans l'application backend. + +>**TIP** +>La tâche `propel:data-load` prend également en charge une option `--application`. +>Donc, si vous avez besoin de paramètres spécifiques d'une ~application|Application~ +>ou une autre, c'est la marche à suivre : +> +> $ php symfony propel:data-load --application=frontend + +Les modules du backend +---------------------- + +Pour l'application frontend, la tâche `propel:generate-module` a été utilisé pour +démarrer un module de base ~CRUD~ basé sur une classe modèle. Pour le backend, la +tâche `propel:generate-admin` sera utilisé car il génère une interface de travail +complète de backend pour une classe de modèle : + + $ php symfony propel:generate-admin backend JobeetJob --module=job + $ php symfony propel:generate-admin backend JobeetCategory + ➥ --module=category + +Ces deux commandes créent un ~module|Module~ `job` et un module `category` pour les +classes respectives du modèle `JobeetJob` et `JobeetCategory`. + +L'option facultative `--module` remplace le nom `module` généré par +défaut par la tâche (ce qui aurait été autrement `jobeet_job` pour la +classe `JobeetJob`). + +Derrière le rideau, la tâche a également créé une route personnalisée pour chaque module : + + [yml] + # apps/backend/config/routing.yml + jobeet_job: + class: sfPropelRouteCollection + options: + model: JobeetJob + module: job + prefix_path: job + column: id + with_wildcard_routes: true + +Il ne faut donc pas s'étonner que la classe de la route utilisée par l'~admin +generator|Admin Generator~ est `sfPropelRouteCollection`, car l'objectif principal +d'une interface d'administration est la gestion du cycle de vie des objets du modèle. + +La définition de la route définit également certaines options que nous n'avons pas vu auparavant : + + * `prefix_path`: Définit le chemin du préfixe pour la route générée (par + exemple, la page d'édition sera quelque chose comme + `/job/1/edit`). + * `column`: Définit la colonne de table à utiliser dans l'URL pour les liens + faisant référence à un objet. + * `with_wildcard_routes`: Comme l'interface d'administration aura plus que des + opérations classiques du CRUD, cette option permet de + définir plus d'objet et une collection d'actions sans + modifier la route. + +>**TIP** +>Comme toujours, c'est une bonne idée de lire l'aide avant d'utiliser une nouvelle tâche. +> +> $ php symfony help propel:generate-admin +> +>Elle vous donnera tous les arguments de la tâche et les options ainsi que +>quelques exemples d'utilisation classique. + +Backend Look and Feel +--------------------- + +D'emblée, vous pouvez utiliser les modules générés : + + http://jobeet.localhost/backend_dev.php/job + http://jobeet.localhost/backend_dev.php/category + +Les modules admin ont beaucoup plus de fonctionnalités que les modules simples +que nous avons générés les jours précédents. Sans écrire une seule ligne de PHP, +chaque module fournit ces fonctionnalités exceptionnelles : + + * La liste des objets est **paginée** + * La liste est **triable** + * La liste peut être **filtrée** + * Les objets peuvent être **créés**, **édités** et **supprimés** + * Les objets sélectionnés peuvent être supprimés dans un **batch** + * La **validation** du formulaire est activée + * Les **messages flash** donne immédiatement un retour à l'utilisateur + * ... et plus encore + +L'admin generator offre toutes les fonctions dont vous avez besoin pour créer +une interface backend dans un simple package à configurer. + +Pour rendre l'expérience utilisateur un peu mieux, nous avons besoin de personnaliser +le backend par défaut. Nous allons aussi ajouter un menu simple pour le rendre facile +pour naviguer entre les différents modules. + +Remplacez le contenu du fichier `~layout|Layout~.php` par défaut avec le code ci-dessous : + + [php] + // apps/backend/templates/layout.php + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>Jobeet Admin Interface</title> + <link rel="shortcut icon" href="/favicon.ico" /> + <?php use_stylesheet('admin.css') ?> + <?php include_javascripts() ?> + <?php include_stylesheets() ?> + </head> + <body> + <div id="container"> + <div id="header"> + <h1> + <a href="<?php echo url_for('@homepage') ?>"> + <img src="/images/logo.jpg" alt="Jobeet Job Board" /> + </a> + </h1> + </div> + + <div id="menu"> + <ul> + <li> +<propel> + <?php echo link_to('Jobs', '@jobeet_job') ?> +</propel> +<doctrine> + <?php echo link_to('Jobs', '@jobeet_job_job') ?> +</doctrine> + </li> + <li> +<propel> + <?php echo link_to('Categories', '@jobeet_category') ?> +</propel> +<doctrine> + <?php echo link_to('Categories', '@jobeet_category_category') ?> +</doctrine> + </li> + </ul> + </div> + + <div id="content"> + <?php echo $sf_content ?> + </div> + + <div id="footer"> + <img src="/images/jobeet-mini.png" /> + powered by <a href="http://www.symfony-project.org/"> + <img src="/images/symfony.gif" alt="symfony framework" /></a> + </div> + </div> + </body> + </html> + +Cette mise en page utilise une feuille de style `admin.css`. Ce fichier doit être déjà présent +dans `web/css/` car il a été installé avec les autres feuilles de style au cours de jour 4. + + + +Éventuellement, changez la ~page d'accueil|Page d'accueil~ par défaut de symfony dans `routing.yml` : + + [yml] + # apps/backend/config/routing.yml + homepage: + url: / + param: { module: job, action: index } + +Le cache de symfony +------------------- + +Si vous êtes curieux, vous avez probablement déjà ouvert les fichiers générés +par la tâche sous le répertoire `apps/backend/modules/`. Sinon, s'il vous plaît +ouvrez-les maintenant. Surprise ! Les répertoires `templates` sont vides et les +fichiers `actions.class.php` sont assez vides : + + [php] + // apps/backend/modules/job/actions/actions.class.php + require_once dirname(__FILE__).'/../lib/jobGeneratorConfiguration.class.php'; + require_once dirname(__FILE__).'/../lib/jobGeneratorHelper.class.php'; + + class jobActions extends autoJobActions + { + } + +Comment peut-il fonctionner ? Si vous regardez d'un peu plus près, vous remarquerez +que la classe `jobActions` étend `autoJobActions`. La classe `autoJobActions` est +automatiquement généré par symfony si elle n'existe pas. Elle se trouve dans le +répertoire `cache/backend/dev/modules/autoJob/`, qui contient le module +«réel» : + + [php] + // cache/backend/dev/modules/autoJob/actions/actions.class.php + class autoJobActions extends sfActions + { + public function preExecute() + { + $this->configuration = new jobGeneratorConfiguration(); + + if (!$this->getUser()->hasCredential( + $this->configuration->getCredentials($this->getActionName()) + )) + { + + // ... + +La manière dont l'admin generator fonctionne devrait vous rappeler certains comportements +connus. En fait, il est assez semblable à ce que nous avons appris sur le modèle et les +classes du formulaire. Basé sur la définition du schéma du modèle, symfony génère le +modèle et les classes du formulaire. Pour l'admin generator, le module généré peut être +configuré en éditant le fichier `config/generator.yml` trouvé dans le module : + + [yml] + # apps/backend/modules/job/config/generator.yml + generator: + class: sfPropelGenerator + param: + model_class: JobeetJob + theme: admin + non_verbose_templates: true + with_show: false + singular: ~ + plural: ~ + route_prefix: jobeet_job +<propel> + with_propel_route: 1 +</propel> +<doctrine> + with_doctrine_route: 1 +</doctrine> + + config: + actions: ~ + fields: ~ + list: ~ + filter: ~ + form: ~ + edit: ~ + new: ~ + +Chaque fois que vous mettez à jour le fichier `generator.yml`, symfony régénère le +cache. Comme nous le voyons aujourd'hui, la personnalisation de l'admin des modules +générés est simple, rapide et amusant. + +>**NOTE** +>La re-génération automatique des fichiers de ~cache|Cache~ se produit uniquement dans +>l'environnement de développement. En production, vous devez effacer le cache manuellement +>avec la tâche `cache:clear`. + +La configuration du backend +--------------------------- + +Un module admin peut être personnalisé en modifiant la clé `config` du +fichier `generator.yml`. La configuration est organisée en sept sections : + + * `actions`: La configuration par défaut pour les actions figurant sur la liste et + dans les formulaires + * `fields`: La configuration par défaut pour les champs + * `list`: La configuration pour la liste + * `filter`: La configuration pour les filtres + * `form`: La configuration pour le formulaire new/edit + * `edit`: La configuration spécifique pour la page edit + * `new`: La configuration spécifique pour la page new + +Commençons la personnalisation. + +Configuration du titre +---------------------- + +Les titres des sections `list`, `edit` et `new` du module `category` peuvent être +personnalisés en définissant l'option `title` : + + [yml] + # apps/backend/modules/category/config/generator.yml + config: + actions: ~ + fields: ~ + list: + title: Category Management + filter: ~ + form: ~ + edit: + title: Editing Category "%%name%%" + new: + title: New Category + +Le `title` pour la section `edit` contient des valeurs dynamiques : toutes les chaînes +entourées par `%%` sont remplacées par les valeurs de la colonne pour l'objet +correspondant. + + + +La configuration pour le module `job` est assez similaire : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + actions: ~ + fields: ~ + list: + title: Job Management + filter: ~ + form: ~ + edit: + title: Editing Job "%%company%% is looking for a %%position%%" + new: + title: Job Creation + +Configuration des champs +------------------------ + +Les différentes vues (`list`, `new` et `edit`) sont composés de +~champs|Chamas~. Un champ peut être une colonne de la classe du modèle, ou une +colonne virtuelle comme nous le verrons plus tard. + +La configuration des champs par défaut peut être personnalisée avec la section `fields` : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + fields: + is_activated: { label: Activated?, help: Whether the user has activated the job, or not } + is_public: { label: Public?, help: Whether the job can also be published on affiliate websites, or not } + + + +La section `fields` remplace la configuration des champs pour toutes les vues, ce qui +signifie que le `label` pour le champ `is_activated` sera changé pour les vues +`list`, `edit` et `new`. + +La configuration de l'admin generator est basé sur un principe de configuration +en cascade. Par exemple, si vous souhaitez modifier un ~label|Labels de formulaire~ +pour la vue `list` uniquement, définissez une option `fields` sous la section `list` : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + fields: + is_public: { label: "Public? (label for the list)" } + +Toute configuration qui est définie dans la section principale `fields` peut +être substituée par la configuration de la vue spécifique. Les règles de +substitution sont les suivantes : + + * `new` et `edit` héritent de `form` qui hérite `fields` + * `list` hérite `fields` + * `filter` hérite `fields` + +>**NOTE** +>Pour les sections de formulaire (`form`, `edit` et `new`), les options `label` +>et `help` substituent celles définies dans les classes de formulaires. + +Configuration de la vue list +---------------------------- + +### `display` + +Par défaut, les colonnes de la vue de la liste sont toutes des colonnes du modèle, dans +l'ordre du fichier du schéma. L'option `display` substitue l'ordre par défaut +des colonnes à afficher : + + [yml] + # apps/backend/modules/category/config/generator.yml + config: + list: + title: Category Management + display: [=name, slug] + +Le signe `=` avant la colonne `name` est une convention visant à convertir la chaîne +en un ~lien|Liens~. + + + +Faisons de même pour le module `job` pour le rendre plus lisible : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + title: Job Management + display: [company, position, location, url, is_activated, email] + +### `layout` + +La liste peut être affichée avec des ~layout|Layout~s différents. Par défaut, le +layout est `~tabular|Layout tabular~`, ce qui signifie que chaque valeur de colonne +est la colonne dans sa propre table. Mais pour le module `job`, il serait préférable +d'utiliser le layout `~stacked|Layout stacked~`, qui est l'autre layout intégré : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + title: Job Management + layout: stacked + display: [company, position, location, url, is_activated, email] + params: | + %%is_activated%% <small>%%category_id%%</small> - %%company%% + (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%) + +Dans un layout `stacked`, chaque objet est représenté par une seule chaîne, qui +est definie par l'option `params`. + +>**NOTE** +>L'option `display` est toujours nécessaire, car elle définit les colonnes qui seront +>triables par l'utilisateur. + +### Les colonnes "Virtuelles" + +Avec cette configuration, le segment `%%category_id%%` sera remplacée par la clé +primaire de la catégorie. Mais il serait plus utile d'afficher le nom de la +catégorie. + +Chaque fois que vous utilisez la notation `%%`, la variable ne doit pas nécessairement +correspondre à une colonne réelle dans le schéma de la base de données. L'admin generator +a seulement besoin de trouver un getter associé dans la classe du modèle. + +Pour afficher le nom de la catégorie, on peut définir une méthode `getCategoryName()` dans +la classe du modèle `JobeetJob` et remplacer `%%category_id%%` par +`%%category_name%%`. + +Mais la classe `JobeetJob` a déjà une méthode `getJobeetCategory()` qui +retourne l'objet catégorie associé. Et si vous utilisez `%%jobeet_category%%`, cela +fonctionne comme la classe `JobeetCategory`. Cette dernière a une méthode magique +`__toString()` qui convertit l'objet en une chaîne. + + [yml] + # apps/backend/modules/job/config/generator.yml + %%is_activated%% <small>%%jobeet_category%%</small> - %%company%% + (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%) + + + +### `sort` + +En tant qu'administrateur, vous serez probablement plus intéressés de voir les dernières offres +d'emploi postées. Vous pouvez configurer la colonne de tri par défaut en ajoutant l'option `sort` : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + sort: [expires_at, desc] + +### `max_per_page` + +Par défaut, la liste est ~paginée|Pagination~ et chaque page contient 20 +articles. Ceci peut être modifié avec l'option `~max_per_page~` : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + max_per_page: 10 + + + +### `batch_actions` + +Sur une liste, une action peut être exécutée sur plusieurs objets. Ces actions batch +ne sont pas nécessaires pour le module `category`, donc nous allons les supprimer : + + [yml] + # apps/backend/modules/category/config/generator.yml + config: + list: + batch_actions: {} + + + +L'option `batch_actions` définit la liste des actions batch. Le tableau vide permet +l'élimination de la fonctionnalité. + +Par défaut, chaque module a une action batch `delete` définie par le framework, +mais pour le module `job`, supposons que nous devons trouver un moyen de prolonger +la validité de certains emplois sélectionnés pour 30 jours supplémentaires : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + batch_actions: + _delete: ~ + extend: ~ + +Toutes les actions commençant par un `_` sont intégrés dans les actions fournies par +le framework. Si vous actualisez votre navigateur et sélectionnez les actions batch +supplémenaires, symfony va lever une exception en vous invitant à créer une méthode +`executeBatchExtend()` : + + [php] + // apps/backend/modules/job/actions/actions.class.php + class jobActions extends autoJobActions + { + public function executeBatchExtend(sfWebRequest $request) + { + $ids = $request->getParameter('ids'); + +<propel> + $jobs = JobeetJobPeer::retrieveByPks($ids); + + foreach ($jobs as $job) +</propel> +<doctrine> + $q = Doctrine_Query::create() + ->from('JobeetJob j') + ->whereIn('j.id', $ids); + + foreach ($q->execute() as $job) +</doctrine> + { + $job->extend(true); + } + + $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.'); + +<propel> + $this->redirect('@jobeet_job'); +</propel> +<doctrine> + $this->redirect('@jobeet_job_job'); +</doctrine> + } + } + +Les ~clés primaires|Clé primaire~ sélectionnées sont stockées dans le paramètre +de requête `ids`. Pour chaque emploi sélectionné, la méthode `JobeetJob::extend()` est +appelée avec un argument supplémentaire pour contourner le contrôle d'expiration. + +Mettez à jour la méthode `extend()` pour prendre ce nouvel argument en compte : + + [php] +<propel> + // lib/model/JobeetJob.php +</propel> +<doctrine> + // lib/model/doctrine/JobeetJob.class.php +</doctrine> + class JobeetJob extends BaseJobeetJob + { + public function extend($force = false) + { + if (!$force && !$this->expiresSoon()) + { + return false; + } + +<propel> + $this->setExpiresAt(time() + 86400 * sfConfig::get('app_active_days')); +</propel> +<doctrine> + $this->setExpiresAt(date('Y-m-d', time() + 86400 * sfConfig::get('app_active_days'))); +</doctrine> + $this->save(); + + return true; + } + + // ... + } + +Après que tous les emplois aient été étendus, l'utilisateur est redirigé vers la page +d'accueil du module `job`. + + + +### `object_actions` + +Dans la liste, il y a une colonne supplémentaire pour les actions que vous pouvez +exécuter sur un seul objet. Pour le module `category`, retirons les car nous avons un +lien sur le nom de catégorie pour la modifier, et nous n'avons pas vraiment besoin +d'être en mesure d'en supprimer une directement dans la liste : + + [yml] + # apps/backend/modules/category/config/generator.yml + config: + list: + object_actions: {} + +Pour le module `job`, gardons les actions existantes et ajoutons un nouvel `extend` +d'une action semblable à celle que nous avons ajouté pour les actons batch : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + object_actions: + extend: ~ + _edit: ~ + _delete: ~ + +Quant aux actions batch, les actions `_delete` et `_edit` sont celles définies par +le framework. Nous avons besoin de définir l'action `listExtend()` pour faire fonctionner +le lien `extend` : + + [php] + // apps/backend/modules/job/actions/actions.class.php + class jobActions extends autoJobActions + { + public function executeListExtend(sfWebRequest $request) + { + $job = $this->getRoute()->getObject(); + $job->extend(true); + + $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.'); + +<propel> + $this->redirect('@jobeet_job'); +</propel> +<doctrine> + $this->redirect('@jobeet_job_job'); +</doctrine> + } + + // ... + } + + + +### `actions` + +Nous avons déjà vu comment lier une action à une liste d'objets ou à un objet +unique. L'option `actions` définit les actions qui ne prennent pas d'objet du tout, +comme la création d'un nouvel objet. Enlevons l'action par défaut `new` et ajoutons +une nouvelle action, qui supprime tous les emplois qui n'ont pas été activés par +l'employeur pendant plus de 60 jours : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: + actions: + deleteNeverActivated: { label: Delete never activated jobs } + +Jusqu'à présent, toutes les actions que nous avons définies ont `~`, ce qui signifie que +symfony configure automatiquement l'action. Chaque action peut être personnalisée en +définissant un tableau de paramètres. L'option `label` substitue le ~label|Labels~ +par défaut généré par symfony. + +Par défaut, l'action exécutée lorsque vous cliquez sur le lien est le nom de +l'action préfixée par `list`. + +Créez l'action `listDeleteNeverActivated` dans le module `job` : + + [php] + // apps/backend/modules/job/actions/actions.class.php + class jobActions extends autoJobActions + { + public function executeListDeleteNeverActivated(sfWebRequest $request) + { +<propel> + $nb = JobeetJobPeer::cleanup(60); +</propel> +<doctrine> + $nb = Doctrine::getTable('JobeetJob')->cleanup(60); +</doctrine> + + if ($nb) + { + $this->getUser()->setFlash('notice', sprintf('%d never activated jobs have been deleted successfully.', $nb)); + } + else + { + $this->getUser()->setFlash('notice', 'No job to delete.'); + } + +<propel> + $this->redirect('@jobeet_job'); +</propel> +<doctrine> + $this->redirect('@jobeet_job_job'); +</doctrine> + } + + // ... + } + +<propel> +Nous avons réutilisé la méthode `JobeetJobPeer::cleanup()` définie hier. C'est +un autre excellent exemple de la réutilisation fournie par le modèle MVC. +</propel> +<doctrine> +Nous avons réutilisé la méthode `JobeetJobTable::cleanup()` définie hier. C'est +un autre excellent exemple de la réutilisation fournies par le modèle MVC. +</doctrine> + +>**NOTE** +>Vous pouvez également modifier l'action à exécuter en passant le paramètre `action` : +> +> [yml] +> deleteNeverActivated: { label: Delete never activated jobs, action: foo } + + + +<propel> +### `peer_method` +</propel> +<doctrine> +### `table_method` +</doctrine> + +Le nombre de requêtes nécessaires vers la base de données pour afficher la page de la liste +des emplois est de 14, comme illustré par le ~web debug toolbar|Web Debug Toolbar~. + +Si vous cliquez sur le nombre, vous verrez que la plupart des requêtes sont +utilisées pour récupérer le nom de la catégorie pour chaque emploi : + + + +Pour réduire le nombre de requête, nous pouvons changer la méthode par défaut utilisé pour récupérer +<propel> +les emplois en utilisant l'option `peer_method` : +</propel> +<doctrine> +les emplois en utilisant l'option `table_method` : +</doctrine> + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + list: +<propel> + peer_method: doSelectJoinJobeetCategory + +La méthode `doSelectJoinJobeetCategory()` ajoute une jointure entre les tables +`job` et `category` et crée automatiquement l'objet catégorie lié à chaque +emploi. +</propel> +<doctrine> + table_method: retrieveBackendJobList + +Maintenant vous devez créer la méthode `retrieveBackendJobList` dans `JobeetJobTable` +situé dans `lib/model/doctrine/JobeetJobTable.class.php`. + + [php] + // lib/model/doctrine/JobeetJobTable.class.php + class JobeetJobTable extends Doctrine_Table + { + public function retrieveBackendJobList(Doctrine_Query $q) + { + $rootAlias = $q->getRootAlias(); + $q->leftJoin($rootAlias . '.JobeetCategory c'); + return $q; + } + + // ... + +La méthode `retrieveBackendJobList()` ajoute une ~jointure~ entre les tables +`job` et `category` et crée automatiquement l'objet catégorie lié à chaque +emploi. +</doctrine> + +Le nombre de requêtes est maintenant réduit à quatre: + + + +Configuration des vues du formulaire +------------------------------------ + +La configuration des vues du formulaire se fait dans trois sections : `form`, `edit` et +`new`. Elles ont tous les mêmes capacités de configuration et la section `form` existe +seulement en tant que solution de repli pour les sections `edit` et `new`. + +### `display` + +Comme pour la liste, vous pouvez changer l'ordre des champs affichés avec l'option +`display`. Mais comme le formulaire affiché est définie par une classe, n'essayez pas +de supprimer un champ car cela pourrait conduire à des erreurs inattendues. + +L'option `display` pour les vues du formulaire peut également être utilisé pour +organiser des champs en groupes : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + form: + display: + Content: [category_id, type, company, logo, url, position, + ➥ location, description, how_to_apply, is_public, email] + Admin: [_generated_token, is_activated, expires_at] + +La configuration ci-dessus définit deux groupes (`Content` et `Admin`), contenant +chacune un sous-ensemble de champs du formulaire. + + + +>**NOTE** +>Les colonnes dans le groupe `Admin` n'apparaissent pas dans le navigateur pour le moment car +>elles ont été mis hors d'usage dans la définition du formulaire emploi. Elles apparaîtront dans quelques +>sections où nous aurons défini une classe de formulaire d'emplois personnalisée pour l'application admin. + +L'admin generator a un support intégré pour la relation plusieurs vers plusieurs. Sur le +formulaire de catégorie, vous disposez d'une entrée pour le nom, d'une pour le slug et une +liste déroulante pour les affiliés connexes. Comme cela n'a pas de sens de modifier cette +relation sur cette page, nous allons la supprimer : + + [php] +<propel> + // lib/form/JobeetCategoryForm.class.php +</propel> +<doctrine> + // lib/form/doctrine/JobeetCategoryForm.class.php +</doctrine> + class JobeetCategoryForm extends BaseJobeetCategoryForm + { + public function configure() + { +<propel> + unset($this['jobeet_category_affiliate_list']); +</propel> +<doctrine> + unset($this['created_at'], $this['updated_at'], $this['jobeet_affiliates_list']); +</doctrine> + } + } + +### Les colonnes "Virtuelles" + +Dans l'option `display` pour le formulaire d'emplois, le `_generated_token` commence +par un caractère de soulignement (`_`). Cela signifie que le rendu de ce champ sera +traitée par un ~partial|Template partial~ personnalisé nommé `_generated_token.php`. + +Créer ce partial avec le contenu suivant : + + [php] + // apps/backend/modules/job/templates/_generated_token.php + <div class="sf_admin_form_row"> + <label>Token</label> + <?php echo $form->getObject()->getToken() ?> + </div> + +Dans le partial, vous avez accès au formulaire actuel (`$form`) et l'objet +connexe est accessible via la méthode `getObject()`. + +>**NOTE** +>Vous pouvez aussi déléguer le rendu d'un composant en faisant précéder le nom +>du champ par un tilde (`~`). + +### `class` + +Comme le formulaire sera utilisé par les administrateurs, nous avons affiché plus d'informations +que pour le formulaire emploi de l'utilisateur. Mais pour l'instant, certains d'entre eux ne +figurent pas sur le formulaire car ils l'ont été enlevés dans la classe `JobeetJobForm`. + +Pour avoir des formulaires différents pour le frontend et le backend, nous avons besoin +de créer deux classes de formulaire. Nous allons créer une classe `BackendJobeetJobForm` qui +étend la classe `JobeetJobForm`. Comme nous n'aurons pas les mêmes champs cachés, nous devons +aussi un peu refactoriser la classe `JobeetJobForm` pour déplacer l'instruction `unset()` dans +une méthode qui substituera dans `BackendJobeetJobForm` : + + [php] +<propel> + // lib/form/JobeetJobForm.class.php +</propel> +<doctrine> + // lib/form/doctrine/JobeetJobForm.class.php +</doctrine> + class JobeetJobForm extends BaseJobeetJobForm + { + public function configure() + { + $this->removeFields(); + + $this->validatorSchema['email'] = new sfValidatorAnd(array( + $this->validatorSchema['email'], + new sfValidatorEmail(), + )); + + // ... + } + + protected function removeFields() + { + unset( + $this['created_at'], $this['updated_at'], + $this['expires_at'], $this['is_activated'], + $this['token'] + ); + } + } + +<propel> + // lib/form/BackendJobeetJobForm.class.php +</propel> +<doctrine> + // lib/form/doctrine/BackendJobeetJobForm.class.php +</doctrine> + class BackendJobeetJobForm extends JobeetJobForm + { + public function configure() + { + parent::configure(); + } + + protected function removeFields() + { + unset( + $this['created_at'], $this['updated_at'], + $this['token'] + ); + } + } + +La ~classe du formulaire|Formulaires (Classes)~ par défaut utilisé par l'admin +generator peut être surchargée en définissant l'option `class` : + + [yml] + # apps/backend/modules/job/config/generator.yml + config: + form: + class: BackendJobeetJobForm + +>**NOTE** +>Comme nous avons ajouté une nouvelle classe, n'oubliez pas de vider le cache. + +Le formulaire `edit` a toujours un petit ennui. Le logo actuel ~téléchargé|Téléchargement +de fichier~ ne s'affiche pas partout et vous ne pouvez pas supprimer l'actuelle. +Le widget `sfWidgetFormInputFileEditable` ajoute des capacités d'édition pour un widget +d'un simple fichier en entrée : + + [php] +<propel> + // lib/form/BackendJobeetJobForm.class.php +</propel> +<doctrine> + // lib/form/doctrine/BackendJobeetJobForm.class.php +</doctrine> + class BackendJobeetJobForm extends JobeetJobForm + { + public function configure() + { + parent::configure(); + + $this->widgetSchema['logo'] = new sfWidgetFormInputFileEditable(array( + 'label' => 'Company logo', + 'file_src' => '/uploads/jobs/'.$this->getObject()->getLogo(), + 'is_image' => true, + 'edit_mode' => !$this->isNew(), + 'template' => '<div>%file%<br />%input%<br />%delete% %delete_label%</div>', + )); + + $this->validatorSchema['logo_delete'] = new sfValidatorPass(); + } + + // ... + } + +Le widget `sfWidgetFormInputFileEditable` a plusieurs options pour peaufiner ses +caractéristiques et le rendu : + + * `file_src`: Le chemin web pour télécharger le fichier + * `is_image`: Si `true`, le fichier sera rendu comme une image + * `edit_mode`: Si le formulaire est en mode édition ou non + * `with_delete`: S'il faut afficher la case à cocher pour supprimer + * `template`: Le Template à utiliser pour rendre le widget + + + +>**TIP** +>Le look de l'admin generator peut être modifié très facilement car les Templates +>générés définissent beaucoup d'attributs `class` et `id`. Par exemple, le champ du logo +>peut être personnalisé en utilisant la classe `sf_admin_form_field_logo`. Chaque champ a +>également une classe en fonction du type du champ, comme `sf_admin_text` ou +>`sf_admin_boolean`. + +<propel> +L'option `edit_mode` utilise la méthode `sfPropel::isNew()`. +</propel> +<doctrine> +L'option `edit_mode` utilise la méthode `sfDoctrineRecord::isNew()`. +</doctrine> + +Elle retourne `true` si l'objet du modèle du formulaire est nouveau, sinon +`false`. Ceci est d'une grande aide lorsque vous avez besoin d'avoir différents +widgets ou validateurs en fonction du statut de l'objet incorporé. + +Configuration des filtres +------------------------- + +La configuration des filtres est tout à fait la même que la configuration des vues +du formulaire. En fait, les filtres ne sont que des formulaires. Et comme pour les +formulaires, les classes ont été générés par la tâche `propel:build --all`. Vous pouvez +également les re-générer avec la tâche `propel:build --filters`. + +Les classes de ~filtre de formulaire|Formulaires (Classes de filtre)~ sont situées sous +le répertoire `lib/filter/` et chaque classe du modèle est associée à une classe de filtre +de formulaire (`JobeetJobFormFilter` pour `JobeetJobForm`). + +Supprimons-les complètement pour le module `category` : + + [yml] + # apps/backend/modules/category/config/generator.yml + config: + filter: + class: false + +Pour le module `job`, nous allons supprimer certains d'entre eux : + + [yml] + # apps/backend/modules/job/config/generator.yml + filter: + display: [category_id, company, position, description, is_activated, + ➥ is_public, email, expires_at] + +Comme les filtres sont toujours facultatifs, il n'y a pas besoin de surcharger +la classe du formulaire de filtre pour configurer les champs à afficher. + + + +Personnalisation des actions +---------------------------- + +Lorsque la configuration n'est pas suffisante, vous pouvez ajouter de nouvelles +méthodes pour la classe de l'action comme nous l'avons vu avec la fonctionnalité +d'extension, mais vous pouvez aussi surcharger les méthodes de l'action générées : + + | Méthode | Description + | ---------------------- | ------------------------------------- + | `executeIndex()` | Action de la vue `list` + | `executeFilter()` | Mettre à jour les filtres + | `executeNew()` | Action de la vue `new` + | `executeCreate()` | Créer un nouvel emploi + | `executeEdit()` | Action de la vue `edit` + | `executeUpdate()` | Mettre à jour un emploi + | `executeDelete()` | Supprimer un emploi + | `executeBatch()` | Executer une action batch + | `executeBatchDelete()` | Executer l'action batch `_delete` + | `processForm()` | Processer le formulaire emploi + | `getFilters()` | Retourner le filtre actuel + | `setFilters()` | Définir le filtre + | `getPager()` | Retourner la pagination de la liste + | `getPage()` | Obtenir la page de la pagination + | `setPage()` | Définir la page de la pagination + | `buildCriteria()` | Construire le `Criteria` pour la liste + | `addSortCriteria()` | Ajouter le tri `Criteria` pour la liste + | `getSort()` | Retourner la colonne triée actuelle + | `setSort()` | Définit la colonne triée actuelle + +Comme chaque méthode générée ne fait qu'une chose, il est facile de changer un comportement +sans avoir à copier et coller trop de code. + +Personnalisation des Templates +------------------------------ + +Nous avons vu comment personnaliser les ~templates|Templates~ générés grâce aux +attributs `class` et `id` ajoutés par l'admin generator dans le code HTML. + +Quant aux classes, vous pouvez également remplacer les Templates originaux. Comme les +Templates sont des simples fichiers PHP et non des classes PHP, un Template peut être +substituée en créant un Template du même nom dans le module (par exemple dans le répertoire +`apps/backend/modules/job/templates/` pour le module de l'admin `job`) : + + | Template | Description + | ---------------------------- | ------------------------------------- + | `_assets.php` | Rendre lees CSS et les JS pour les utiliser dans les Templates + | `_filters.php` | Rendre la zone des filtres + | `_filters_field.php` | Rendre un seul champ du filtre + | `_flashes.php` | Rendre les messages flash + | `_form.php` | Afficher le formulaire + | `_form_actions.php` | Afficher les actions du formulaire + | `_form_field.php` | Afficher un seul champ du formulaire + | `_form_fieldset.php` | Afficher un jeu de champs du formulaire + | `_form_footer.php` | Afficher le formulaire pied de page + | `_form_header.php` | Afficher le formulaire d'entête + | `_list.php` | Afficher la liste + | `_list_actions.php` | Afficher les actions de la liste + | `_list_batch_actions.php` | Afficher les actions batch de la liste + | `_list_field_boolean.php` | Afficher un seul champ booléen dans la liste + | `_list_footer.php` | Afficher le pied de page de la liste + | `_list_header.php` | Afficher l'entête de la liste + | `_list_td_actions.php` | Afficher les actions d'un objet pour une ligne + | `_list_td_batch_actions.php` | Afficher le checkbox pour une ligne + | `_list_td_stacked.php` | Afficher le layout stacked pour une ligne + | `_list_td_tabular.php` | Afficher un seul champ pour la liste + | `_list_th_stacked.php` | Afficher un seul nom de colonne pour l'entête + | `_list_th_tabular.php` | Afficher un seul nom de colonne pour l'entête + | `_pagination.php` | Afficher la pagination de la liste + | `editSuccess.php` | Afficher la vue `edit` + | `indexSuccess.php` | Afficher la vue `list` + | `newSuccess.php` | Afficher la vue `new` + +Configuration finale +-------------------- + +La configuration finale pour l'admin de Jobeet admin se présente comme suit : + + [yml] + # apps/backend/modules/job/config/generator.yml + generator: + class: sfPropelGenerator + param: + model_class: JobeetJob + theme: admin + non_verbose_templates: true + with_show: false + singular: ~ + plural: ~ + route_prefix: jobeet_job +<propel> + with_propel_route: 1 +</propel> +<doctrine> + with_doctrine_route: 1 +</doctrine> + + config: + actions: ~ + fields: + is_activated: { label: Activated?, help: Whether the user has activated the job, or not } + is_public: { label: Public? } + list: + title: Job Management + layout: stacked + display: [company, position, location, url, is_activated, email] + params: | +<propel> + %%is_activated%% <small>%%jobeet_category%%</small> - %%company%% +</propel> +<doctrine> + %%is_activated%% <small>%%JobeetCategory%%</small> - %%company%% +</doctrine> + (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%) + max_per_page: 10 + sort: [expires_at, desc] + batch_actions: + _delete: ~ + extend: ~ + object_actions: + extend: ~ + _edit: ~ + _delete: ~ + actions: + deleteNeverActivated: { label: Delete never activated jobs } +<propel> + peer_method: doSelectJoinJobeetCategory +</propel> +<doctrine> + table_method: retrieveBackendJobList +</doctrine> + filter: + display: [category_id, company, position, description, is_activated, is_public, email, expires_at] + form: + class: BackendJobeetJobForm + display: + Content: [category_id, type, company, logo, url, position, location, description, how_to_apply, is_public, email] + Admin: [_generated_token, is_activated, expires_at] + edit: + title: Editing Job "%%company%% is looking for a %%position%%" + new: + title: Job Creation + + # apps/backend/modules/category/config/generator.yml + generator: + class: sfPropelGenerator + param: + model_class: JobeetCategory + theme: admin + non_verbose_templates: true + with_show: false + singular: ~ + plural: ~ + route_prefix: jobeet_category +<propel> + with_propel_route: 1 +</propel> +<doctrine> + with_doctrine_route: 1 +</doctrine> + + config: + actions: ~ + fields: ~ + list: + title: Category Management + display: [=name, slug] + batch_actions: {} + object_actions: {} + filter: + class: false + form: + actions: + _delete: ~ + _list: ~ + _save: ~ + edit: + title: Editing Category "%%name%%" + new: + title: New Category + +Avec seulement ces deux fichiers de configuration, nous avons développé une interface +backend idéal pour Jobeet en quelques minutes. + +>**TIP** +>Vous savez déjà que lorsque quelque chose est configurable dans un fichier YAML, il y a +>aussi la possibilité d'utiliser du code PHP. Pour l'admin generator, vous pouvez +>modifier le fichier `apps/backend/modules/job/lib/jobGeneratorConfiguration.class.php`. +>Il vous donne les mêmes options que le fichier YAML mais avec une interface PHP. +>Pour apprendre les noms des méthodes, jetez un oeil à la classe de base générée +>en +>`cache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php`. + +À demain +-------- + +En une heure seulement, nous avons construit entièrement une interface backend pour +le projet Jobeet. Et dans l'ensemble, nous avons écrit au plus 50 lignes de code PHP. +Pas trop mal pour de nombreuses fonctionnalités ! + +Demain, nous allons voir comment sécuriser l'application backend avec un identifiant +et un mot de passe. Ce sera également l'occasion de parler de la classe user de +symfony. + +__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.
