- La création du backend
- Les modules du backend
- Backend Look and Feel
- Le cache de symfony
- La configuration du backend
- Configuration du titre
- Configuration des champs
- Configuration de la vue list
- Configuration des vues du formulaire
- Configuration des filtres
- Personnalisation des actions
- Personnalisation des Templates
- Configuration finale
- Conclusion
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.
Dans ce chapitre, grâce à la fonctionnalité de l'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. Si vous
vous souvenez bien, vous devriez vous rappeler comment le faire avec la tâche
generate:app
:
$ php symfony generate:app backend
L'application backend est maintenant disponible à l'adresse
http://www.jobeet.com.localhost/backend.php/
pour l'environnement de prod
et
http://www.jobeet.com.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
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 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 :
# 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://www.jobeet.com.localhost/backend_dev.php/job http://www.jobeet.com.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.
Si vous tentez de consulter les deux modules d'administration générés, vous
remarquerez qu'aucun style graphique n'est activé pour l'instant alors que le
générateur d'administration de symfony en fournit un par défaut. Il ne s'agit ni
d'un bug ni d'un oubli de la part du développeur. En réalité, les ressources web
du plugin sfPropelPlugin
ne sont pas encore publiées dans le dossier web/
.
Nous devons donc publier ces ressources web dans le répertoire web/
du projet
à l'aide de la commande plugin:publish-assets
:
$ php symfony plugin:publish-assets
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 :
// 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="/legacy/images/logo.jpg" alt="Jobeet Job Board" /> </a> </h1> </div> <div id="menu"> <ul> <li> <?php echo link_to('Jobs', 'jobeet_job') ?> </li> <li> <?php echo link_to('Categories', 'jobeet_category') ?> </li> </ul> </div> <div id="content"> <?php echo $sf_content ?> </div> <div id="footer"> <img src="/legacy/images/jobeet-mini.png" /> powered by <a href="/"> <img src="/legacy/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 du chapitre 4.
Éventuellement, changez la page d'accueil par défaut de symfony dans routing.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 :
// 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» :
// 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 :
# 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 with_propel_route: true 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 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 formulairesfields
: La configuration par défaut pour les champslist
: La configuration pour la listefilter
: La configuration pour les filtresform
: La configuration pour le formulaire new/editedit
: La configuration spécifique pour la page editnew
: 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
:
# 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 :
# 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. 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
:
# 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
pour la vue list
uniquement, définissez une option fields
sous la section list
:
# 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
etedit
héritent deform
qui héritefields
list
héritefields
filter
héritefields
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 :
# 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.
Faisons de même pour le module job
pour le rendre plus lisible :
# 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 layouts différents. Par défaut, le
layout est 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
, qui est l'autre layout intégré :
# 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.
# 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
:
# apps/backend/modules/job/config/generator.yml config: list: sort: [expires_at, desc]
max_per_page
Par défaut, la liste est paginée et chaque page contient 20
articles. Ceci peut être modifié avec l'option max_per_page
:
# 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 :
# 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 :
# 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()
:
// apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeBatchExtend(sfWebRequest $request) { $ids = $request->getParameter('ids'); $jobs = JobeetJobPeer::retrieveByPks($ids); foreach ($jobs as $job) { $job->extend(true); } $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.'); $this->redirect('jobeet_job'); } }
Les clés primaires 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 :
// lib/model/JobeetJob.php class JobeetJob extends BaseJobeetJob { public function extend($force = false) { if (!$force && !$this->expiresSoon()) { return false; } $this->setExpiresAt(time() + 86400 * sfConfig::get('app_active_days')); $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 :
# 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 :
# 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
:
// 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.'); $this->redirect('jobeet_job'); } // ... }
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 :
# 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
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
:
// apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeListDeleteNeverActivated(sfWebRequest $request) { $nb = JobeetJobPeer::cleanup(60); 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.'); } $this->redirect('jobeet_job'); } // ... }
Nous avons réutilisé la méthode JobeetJobPeer::cleanup()
définie dans le précédent
chapitre. C'est un autre excellent exemple de la réutilisation fournie par le modèle MVC.
note
Vous pouvez également modifier l'action à exécuter en passant le paramètre action
:
deleteNeverActivated: { label: Delete never activated jobs, action: foo }
peer_method
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.
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
les emplois en utilisant l'option peer_method
:
# apps/backend/modules/job/config/generator.yml config: list: 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.
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 :
# 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 :
// lib/form/JobeetCategoryForm.class.php class JobeetCategoryForm extends BaseJobeetCategoryForm { public function configure() { unset($this['jobeet_category_affiliate_list']); } }
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 personnalisé nommé _generated_token.php
.
Créer ce partial avec le contenu suivant :
// 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
:
// lib/form/JobeetJobForm.class.php 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'] ); } } // lib/form/BackendJobeetJobForm.class.php class BackendJobeetJobForm extends JobeetJobForm { protected function removeFields() { unset( $this['created_at'], $this['updated_at'], $this['token'] ); } }
La classe du formulaire par défaut utilisé par l'admin
generator peut être surchargée en définissant l'option class
:
# 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 de téléchargement ne s'affiche pas partout et vous ne pouvez pas supprimer l'actuel.
Le widget sfWidgetFormInputFileEditable
ajoute des possibilités d'édition à une
simple saisie de fichier :
// lib/form/BackendJobeetJobForm.class.php 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 fichieris_image
: Sitrue
, le fichier sera rendu comme une imageedit_mode
: Si le formulaire est en mode édition ou nonwith_delete
: S'il faut afficher la case à cocher pour supprimertemplate
: 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
.
L'option edit_mode
utilise la méthode sfPropel::isNew()
.
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 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
:
# apps/backend/modules/category/config/generator.yml config: filter: class: false
Pour le module job
, nous allons supprimer certains d'entre eux :
# 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 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 :
# 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 with_propel_route: true 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: | %%is_activated%% <small>%%jobeet_category%%</small> - %%company%% (<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 } peer_method: doSelectJoinJobeetCategory 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 with_propel_route: true 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
.
Conclusion
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.
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.