Dans une application MVC qui a besoin de faire persister des données, les contrôleurs doivent réaliser 4 opérations de base qu'on désigne souvent par l'abréviation CRUD : Create, Read, Update et Delete. Pour une application développée avec le Zend Framework, un contrôleur est en fait une classe dérivée de Zend_Controller_Action. Afin d'éviter de dupliquer inutilement du code, les développeurs choisissent souvent d'écrire une classe de base qui fournira une implémentation standard de ces opérations.
Un contrôleur remplit énormément de fonctions et est très intimement lié à l'application. Construire un contrôleur CRUD générique qui pourra être réutilisé entre plusieurs applications n'est pas une chose simple et risque d'engendrer un monstre très compliqué et bourré d'options de configuration. De plus, chaque développeur a ses préférences et chaque application ses exigences. C'est pourquoi au lieu de vous proposer une classe toute faite, je vais développer dans plusieurs articles car le sujet est très vaste, les différentes étapes pour développer une classe CRUD en proposant à chaque fois des options que vous pourrez choisir d'ajouter ou non à vos contrôleurs.
Les 4 opérations CRUD vont se traduire dans notre contrôleur par 4 actions, donc 4 méthodes dont le nom se terminera par Action. Souvent l'opération Read se décomposera en deux opérations : index qui liste les éléments de la collection et view qui affiche une vue détaillée d'un des éléments. Pour les opérations create et update, je préfère personnellement utiliser les termes add et edit. Le squelette de notre classe ressemblera donc à ceci :
<?php
abstract class Wiip_Controller_Action_CRUD extends Zend_Controller_Action
{
public function addAction()
{
}
public function deleteAction()
{
}
public function editAction()
{
}
public function indexAction()
{
}
public function viewAction()
{
}
}
?>
On déclare la classe comme abstraite puisqu'on ne va jamais l'instancier directement mais s'en servir comme base pour nos contrôleurs.
Pour accéder au modèle, on a besoin de connaitre le nom de la classe qui lui est associée. Afin éviter d'avoir à le spécifier à chaque nouveau contrôleur, on va suivre une convention de nommage qui va permettre de déduire le nom du modèle d'après le nom du contrôleur.
La convention de nommage utilisée pour le nom des contrôleurs suit la convention UpperCamelCase. Le nom de la classe commence donc par une majuscule ainsi que chaque mot composant le nom du contrôleur. On ajoute ensuite le suffixe Controller. Les URL restent elles toujours en minuscules avec chaque mot séparé par un tiret ou par un point. Exemple :
Pour ce qui est du nom du modèle, le guide du démarrage du ZF utilise la convention suivante pour les modèles : Module_Model_Nom_Du_Modele. Si on reprend l'exemple précédent, cela donnera Default_Model_Posts_Comments.
<?php
abstract class Wiip_Controller_Action_CRUD extends Zend_Controller_Action
{
/**
* Nom de la classe associée à la table
* Optionnel. Si non spécifié, on le déduit du nom de la classe du contrôleur
*
* @var string
*/
protected $_tableClass;
[..]
/**
* Retourne le nom de la classe associée à la table
*
* @return string
*/
protected function _getTableClass()
{
if (!$this->_tableClass) {
// Récupère le nom de la classe du contrôleur
$cls = get_class($this);
// Supprime le suffixe Controller
$this->_tableClass = substr($cls, 0, strlen($cls) - 10);
// Passe de la notation UpperCamelCase à une notation ou les mots sont
// séparés par des blancs soulignés
$this->_tableClass = preg_replace('/([a-z])([A-Z])/', '$1_$2', $this->_tableClass);
// Récupère le nom du module et mets en majuscule la première lettre
$module = ucfirst($this->_getParam('module'));
// Assemble les différents éléments pour obtenir le nom de la classe du modèle
$this->_tableClass = $module . '_Model_' . $this->_tableClass;
}
return $this->_tableClass;
}
[..]
}
?>
Rien de plus simple une fois qu'on a le nom de la classe :
<?php
abstract class Wiip_Controller_Action_CRUD extends Zend_Controller_Action
{
/**
* Instance du modèle
*
* @var $_table Wiip_Model_Abstract
*/
protected $_table;
[..]
/**
* Retourne une instance du modèle
*
* @return Zend_Db_Table
*/
protected function _getTable()
{
if (!$this->_table) {
$tableClass = $this->_getTableClass();
$this->_table = new $tableClass;
}
return $this->_table;
}
[..]
}
?>
A l'usage, on se rend compte qu'on a besoin d'accéder au modèle pratiquement pour chaque requête. Pour éviter d'avoir à appeler systématiquement la méthode _getTable, on peut appeler cette dernière dans le constructeur :
<?php
abstract class Wiip_Controller_Action_CRUD extends Zend_Controller_Action
{
[..]
public function __construct(Zend_Controller_Request_Abstract $request,
Zend_Controller_Response_Abstract $response, array $invokeArgs = array())
{
// On crée automatiquement le modèle avant d'appeler le constructeur
// de Zend_Controller_Action. Comme ça, le modèle sera disponible
// dans les méthodes init.
$this->_getTable();
parent::__construct($request, $response, $invokeArgs);
}
[..]
}
?>
Pour les opérations delete, edit et view, la première étape sera de retrouver l'enregistrement. On peut factoriser le code nécessaire en le plaçant dans une méthode dédiée :
<?php
abstract class Wiip_Controller_Action_CRUD extends Zend_Controller_Action
{
/**
* Enregistrement sur lequel les opérations sont effectuées
*
* @var Zend_Db_Table_Row
*/
protected $_row;
[..]
/**
* Retrouve l'enregistrement
*
* @return Zend_Db_Table_Row
*/
protected function _getRow()
{
if (!$this->_hasParam('id')) {
throw new Zend_Controller_Action_Exception('Pas d\'ID dans les paramètres', 500);
}
$this->_row = $this->_table->find($this->_getParam('id'))->current();
if (!$this->_row) {
throw new Zend_Controller_Action_Exception('Document non trouvé', 404);
}
return $this->_row;
}
[..]
}
?>
Si l'ID passé en argument de la requête ne correspond pas à un enregistrement, on affiche une erreur 404 (page non trouvée).
On verra comment implémenter les méthodes delete, add, edit et view et comment générer et traiter les formulaires.
Commentaires
TheSorrow (non vérifié)
lun, 10/08/2009 - 12:38
Permalink
Juste une précision : Le
Juste une précision :
Le faite de créer une classe qui étend Zend_Controller_Action et qui sert de base à tout les contrôleur n'est pas très flexible (et peut être pas compatible avec de future version du framework) . Le mieux je pense est de créer une interface du type
XX_Controller_Action_Crud qui impose l'implémentation des méthodes CRUD. Ensuite il suffit de créer des aides d'actions qui seront appelées dans les différents contrôleurs.
Voici ce que précise la doc zend :
Action Helpers allow developers to inject runtime and/or on-demand functionality into any Action Controllers that extend Zend_Controller_Action. Action Helpers aim to minimize the necessity to extend the abstract Action Controller in order to inject common Action Controller functionality.
ou encore :
Many tutorials on Zend Framework would have you believe you should create a base class extending Zend_Controller_Action to provide base functionality for your controllers:
However, this is not only not necessary, but typically not a great move for extensibility. You may find later that a given controller only needs a subset of the methods in your base controller -- or that you're constantly adding methods that only a few of your controllers need, creating bloat.
Sinon bon tuto :)
Maxence
lun, 10/08/2009 - 13:39
Permalink
Je pense qu'il vaut mieux
Je pense qu'il vaut mieux réserver les aides d'action aux fonctions optionnelles comme le support d'Ajax par exemple. C'est vrai qu'elles peuvent apporter une grande flexibilité, mais le CRUD, c'est quand même la fonctionnalité de base du contrôleur et donc je pense que c'est plus simple et plus efficace d'étendre Zend_Controller_Action.
Pour l'interface, ça peut être effectivement intéressant si on a plusieurs types de modèles. On pourrait alors avoir une interface XX_Controller_Action_Crud_Interface et des implémentations XX_Controller_Action_Crud_ZendDbTable ou XX_Controller_Action_Crud_Doctrine par exemple.
sklep rowerowy (non vérifié)
jeu, 20/08/2009 - 19:47
Permalink
Thanks
Thanks
Nath
lun, 07/09/2009 - 13:21
Permalink
Merci pour ce tuto :) A quand
Merci pour ce tuto :)
A quand la suite ?
Maxence
mar, 08/09/2009 - 08:42
Permalink
Cette semaine, ça va être
Cette semaine, ça va être chaud (retour de vacances). Peut-être la semaine prochaine.
Sandra (non vérifié)
ven, 26/02/2010 - 11:22
Permalink
A quand la suite de ce super
A quand la suite de ce super tuto ;-)
shady (non vérifié)
lun, 19/07/2010 - 01:22
Permalink
Bonjour, Une suite est-elle
Bonjour,
Une suite est-elle prévu pour cette excellent tuto?
Ajouter un commentaire