Qui l’eu CRUD ? Ou comment créer un thème pour les CRUD Doctrine de Symfony.

Symfony propose des outils très puissants pour faciliter le développement d’applications et surtout la génération des modules grâce à l’Admin Generator et le CRUD. Chacunes de ces solutions à ses avantages et ses inconvénients.

L’admin generator permet en une commande d’avoir un module complet et fonctionnel avec de nombreuses fonctionnalités, le tout relativement configurable. En contre partie, l’ajout de fonctionnalités spécifiques et la mise en forme peuvent rapidement s’avérer fastidieuses. Être obligé d’aller fouiller dans le cache pour aller faire des copier/coller afin de pouvoir surcharger une action, c’est comme qui dirait, bien mais pas top…

D’autre part le CRUD permet lui aussi de générer en une seule commade un module, mais ce module est plus que limité et on se retrouve systématiquement à ré-implémenter les mêmes fonctionnalités de base comme le pager, les filters, etc. Par contre travailler avec un CRUD permet de garder la main sur le code, ce qui n’est pas négligeable.

C’est pourquoi je vous propose de voir comment fabriquer un thème pour le CRUD de manière à l’enrichir de quelques fonctionnalités indispensables pour ne pas avoir à les ré-écrire systematiquement et ainsi ganger du temps. Je vais en profiter pour développer ce thème au sein d’un plugin de manière à pouvoir s’en resservir facilement.

Pour se faire je vais partir d’un projet vierge type sandbox auquel je rajoute le plugin sfTaskExtraPlugin qui facilite grandement la génération de plugins.
Pour me simplifier la vie, je vais reprendre le schema.yml et les fixtures (affiliates.yml, category.yml et jobs.yml) de Jobeet pour avoir un modèle sur lequel m’appuyer pour les exemples.

(Attention le fichier de fixture category.yml comporte les traductions, alors que le schema n’a pas le behavior I18n. Il faut au choix modifier le schema.yml pour rajouter le behavior ou modifier les categories pour supprimer les traductions)

Un petit ./symfony doctrine:build –all –and-load et nous voilà parti.

Let’s rock!

Pour fabriquer un module de type CRUD symfony se sert de template de code pour générer les actions et les vues. Ces fichiers sont bien cachés au fin fond du symfony ! On les trouve dans

/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/default

On va donc utiliser le thème par défaut que l’on va enrichir.

Tout d’abord, on va générer l’arborescence du plugin grâce à la commande generate:plugin de sfTaskExtraPlugin.

?View Code CONSOLE
./symfony generate:plugin myCrudThemePlugin

Puis on prépare l’arborescence qui va accueillir les fichiers du template à l’intérieur du plugin.

?View Code CONSOLE
mkdir -p plugins/myCrudThemePlugin/data/generator/sfDoctrineModule/myCrudTheme

Et on y copie tous les fichiers du thème par défaut.

?View Code CONSOLE
cp -r lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/default/* plugins/myCrudThemePlugin/data/generator/sfDoctrineModule/myCrudTheme

Si vous avez fait un checkout de symfony, les fichiers que vous avez copiés contiennent les informations de svn. Il faut nettoyer tout ça. Je vous propose une petite ligne de commande qui permet de faire ca.

?View Code CONSOLE
find plugins/myCrudThemePlugin/data/generator/sfDoctrineModule/myCrudTheme -name .svn -exec rm -rf {} \;

Les fichiers sont prêts. A ce stade, on pourrait déjà générer un thème en utilisant notre thème. Il sufirait d’activer le plugin dans

/config/ProjectConfiguration.class.php

<?php
 
class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $this->enablePlugins(array(
      ...
      'myCrudThemePlugin',
    ));
  }
}

Et de lancer la commande :

?View Code CONSOLE
./symfony doctrine:generate-module --theme="myCrudTheme" frontend job JobeetJob

Mais ne le faites pas ! :p Ca n’aurait pas un grand intérêt étant donné que l’on a rien changé par rapport au thème par défaut.

Nous allons commencer par rajouter un pager sur la page de listing. Il faut modifier l’action dans

/plugins/myCrudThemePlugin/data/generator/sfDoctrineModule/myCrudTheme/parts/indexAction.php

 
  public function executeIndex(sfWebRequest $request)
  {
    $query = Doctrine_Core::getTable('<?php echo $this->getModelClass() ?>')->createQuery('<?php echo strtolower(substr($this->getModelClass(), 0, 1)) ?>');
 
    $this->pager = new sfDoctrinePager('<?php echo $this->getModelClass() ?>', sfConfig::get('app_myCrudThemePlugin_pagination', 20));
    $this->pager->setQuery($query);
    $this->pager->setPage($this->getRequestParameter('page', 1));
    $this->pager->init();
  }

Pour l’affichage du pager, je vous propose de créer un partial que l’on va inclure dans un module du plugin. Etant donné que le fonctionnement est générique, on a pas besoin qu’il soit recopié systèmatiquement dans les modules générés.

On va générer un module appelé shared qui va accueillir le partial du pager et éventuellement d’autres éléments que l’on voudra mutualiser.
La encore sfTaskExtraPlugin nous aide bien car il intégre une commande qui fais ca.

?View Code CONSOLE
./symfony generate:plugin-module myCrudThemePlugin shared

Il nous reste à aller créer notre partial dans

/plugins/myCrudThemePlugin/modules/shared/templates/_pager.php

<?php if ($pager->haveToPaginate()): ?>
  <div class="pagination">
    <a title="First Page" href="<?php echo (isset($object)) ? url_for($route, $object) : url_for($route) ?>?page=<?php echo $pager->getFirstPage() ?>">« First</a>
    <a title="Previous Page" href="<?php echo (isset($object)) ? url_for($route, $object) : url_for($route) ?>?page=<?php echo $pager->getPreviousPage() ?>">« Previous</a>
<?php foreach ($pager->getLinks() as $page): ?>
    <a title="<?php echo $page ?>" class="number<?php echo ($page == $pager->getPage()) ? ' current' : '' ?>" href="<?php echo (isset($object)) ? url_for($route, $object) : url_for($route) ?>?page=<?php echo $page ?>"><?php echo $page ?></a>
<?php endforeach ?>
    <a title="Next Page" href="<?php echo (isset($object)) ? url_for($route, $object) : url_for($route) ?>?page=<?php echo $pager->getNextPage() ?>">Next »</a>
    <a title="Last Page" href="<?php echo (isset($object)) ? url_for($route, $object) : url_for($route) ?>?page=<?php echo $pager->getLastPage() ?>">Last »</a>
  </div>
  <div class="clear"></div>
<?php endif; ?>

Un pager générique qui fonctionne pour les sfDoctrineRoute et les sfRequestRoute. Il prends deux paramètres, route (obligatoire) et object (facultatif).

On modifie la vue indexSuccess.php pour la faire fonctionner avec le pager et ajouter le partial du pager.

Dans

/plugins/myCrudThemePlugin/data/generator/sfDoctrineModule/myCrudTheme/template/templates/indexSuccess.php

<h1><?php echo sfInflector::humanize($this->getPluralName()) ?> List</h1>
 
<table>
  <thead>
    <tr>
<?php foreach ($this->getColumns() as $column): ?>
      <th><?php echo sfInflector::humanize(sfInflector::underscore($column->getPhpName())) ?></th>
<?php endforeach; ?>
    </tr>
  </thead>
  <tbody>
    [?php foreach ($pager->getResults() as $<?php echo $this->getSingularName() ?>): ?]
    <tr>
<?php foreach ($this->getColumns() as $column): ?>
<?php if ($column->isPrimaryKey()): ?>
<?php if (isset($this->params['route_prefix']) && $this->params['route_prefix']): ?>
 
      <td><a href="[?php echo url_for('<?php echo $this->getUrlForAction(isset($this->params['with_show']) && $this->params['with_show'] ? 'show' : 'edit') ?>', $<?php echo $this->getSingularName() ?>) ?]">[?php echo $<?php echo $this->getSingularName() ?>->get<?php echo sfInflector::camelize($column->getPhpName()) ?>() ?]</a></td>
<?php else: ?>
      <td><a href="[?php echo url_for('<?php echo $this->getModuleName() ?>/<?php echo isset($this->params['with_show']) && $this->params['with_show'] ? 'show' : 'edit' ?>?<?php echo $this->getPrimaryKeyUrlParams() ?>) ?]">[?php echo $<?php echo $this->getSingularName() ?>->get<?php echo sfInflector::camelize($column->getPhpName()) ?>() ?]</a></td>
<?php endif; ?>
<?php else: ?>
      <td>[?php echo $<?php echo $this->getSingularName() ?>->get<?php echo sfInflector::camelize($column->getPhpName()) ?>() ?]</td>
<?php endif; ?>
<?php endforeach; ?>
    </tr>
    [?php endforeach; ?]
  </tbody>
</table>
 
[?php include_partial('shared/pager', array('pager' => $pager, 'route' => '<?php echo (isset($this->params['route_prefix']) && $this->params['route_prefix']) ? $this->getUrlForAction('index') : $this->getModuleName().'/index' ?>')) ?]
 
<?php if (isset($this->params['route_prefix']) && $this->params['route_prefix']): ?>
  <a href="[?php echo url_for('<?php echo $this->getUrlForAction('new') ?>') ?]">New</a>
<?php else: ?>
  <a href="[?php echo url_for('<?php echo $this->getModuleName() ?>/new') ?]">New</a>
<?php endif; ?>

On peut d’ores et déjà générer le crud pour tester,

?View Code CONSOLE
./symfony doctrine:generate-module --theme="myCrudTheme" frontend job JobeetJob

Et vous voilà libéré de la tâche rébarbative des pagers ! :p

Ca faisait un petit moment que je voulais traiter ce sujet sur le blog, et je comptais continuer en intégrant les formFilter. Malheureusement, je manque de temps pour aller plus loin.
Mais vous avez désormais en votre possession les éléments pour pouvoir faire votre propre thème et l’enrichir à votre guise.

J’espére que ca vous aura été utile et si j’en ai le temps je ferais un prochain post pour voir cette intégration des formFilter 😉

Partagez cet article