Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 23-11-2007 16:20:58

2mx
Membre
Lieu: Grenoble
Date d'inscription: 06-08-2007
Messages: 125

CRUD best practice ?

Introduction

Je n'ai pas la prétention de vous donner ici les meilleures pratiques  mais je suis plutôt en recherche d'informations pour créer un contrôleur Crud afin accélérer mes temps de développement.

CRUD ? = Create, read, update and delete
http://fr.wikipedia.org/wiki/CRUD
http://en.wikipedia.org/wiki/Create%2C_ … and_delete

Dés que l'on crée une appli, notamment avec une section admin, on se retrouve toujours avec la même problématique :

* lister, trier, filtrer les enregistrements
* Afficher un enregistrement
* Ajouter un nouvel enregistrement
* Modifier un enregistrement
* Supprimer un enregistrement


Et si l'on fait tout à la mano cela devient vite barbare !

Avant de passer sous ZF je travaillais sous php4 + dreamweaver +  mxkollection. Mxkollection (http://www.interaktonline.com/) est en quelque sorte un framework RAD avec des outils de génération de code (pseudo object php4) avec lesquels il faut environ 10min pour créer un listing + le crud.

Mon objectif est donc de retrouver la même efficacité avec ZF smile

Pour la partie listing je suis en train de développer un Mmx_Data_Grid et pour la partie CRUD j'ai commencé à potasser la question et développer un proto Mmx_Controller_Crud qui est un contrôleur abstrait. J'aimerais avoir votre avis dessus.





CRUD Controller

Les actions  d'insertion, de mise à jour et de suppression obéissent toutes à une même logique et ce quelle que soit la table sur laquelle on travaille. J'ai donc essayé d'isoler cette logique dans un contrôleur abstrait.



La partie suppression est assez directe je n'en parlerai pas ici, et l'insertion et la mise à jour obéissent quasiment à la même logique que l'on peut découper en 3 grandes parties :

1) Initialisation du formulaire
2) Affichage
3) Traitement


Un schéma va parler plus rapidement alors le voici :

http://2mx.fr/medias/forums/ZF/Crud_flowchart.png

On voit bien sur ce schéma que l'insert et l'update ne diffèrent qu'au niveau de l'initialisation et de la requête SQL . Je n'ai donc qu'une Action et qu'une vue pour gérer l'insert et l'update.


En simplifiant on a :

Formulaire envoyé ?

    Non => isInsert ?
                    Oui  => on donne des valeur par défaut au formulaire
                    Non => on récupère les données en bd et on initialise le formulaire
                => Affichage du formulaire


    Oui  => est ce que les données sont valides ?
                     Non => Affiche le formulaire avec les message d'erreurs
                     Oui =>  isInsert ?
                                      Oui   => insert()
                                       Non => Update()


Le crud contrôleur est composé de 4 Actions :

IndexAction() :  affiche le dataGrid
addAction():     ajoute un enregistrement, forward to EditAction()
EditAction():     gère l'insertion (Insert) et la mise à jour (Update)
DeleteAction(): supprime un enregistrement



                     
Insert ou Update ?
Pour savoir si j'ai une insertion ou une mise à jour je suis parti de la convention suivante :
Pour une «Update» je passe la clé primaire avec sa valeur dans l'url. Par exemple pour la gestion des utilisateurs, on aura :

/admin/users/crud/add                     =    Insert
/admin/users/crud/edit/user_id/4     =    Update

Il faut donc signaler au contrôleur quel est le nom de la clé primaire. On le fait dans la méthode Init() avec
$this->_setPrimaryKeyName('user_id');

On peut ensuite savoir si l'on a une action de type Insert avec la méthode
$this->_isInsert();

Pour savoir si le formulaire  a été envoyé j'utilise
$this->getRequest()->isPost()



Stockage et validation des données du formulaire  ?
Pour stocker les données de formulaire j'utilise Mmx_Form qui est un simple conteneur pour les variables composant le formulaire.
Mmx_Form contient aussi une instance de Zend_Input_Filter pour la validation et le filtrage. L'avantage avec  Zend_Input_Filter est que les règles de validation sont définies dans un tableau php qui pourra servir dans un view helper pour afficher les règles de validation javascript coté client. Mmx_Form ajoute aussi quelques méthodes pour notamment savoir si il y a des erreurs associées à un champ.


Le code :


Mmx_Controller_Crud


Code:

<?php
/**
 * Basic Crud Controller
 */
abstract class Mmx_Controller_Crud extends Zend_Controller_Action
{
    /**
     * Variable d'url qui contient l'identifiant de la clé primaire
     * @var string
     */
    protected $_paramPkName;

    /**
     * Détermine si l'on a un insert ou une update
     * si l'on a un paramètre $_paramPkName dans l'url on a une update ou delete => $_isInsert = false
     * @var boolean
     */
    protected $_isInsertAction = true;

    /**
     * Une instance Mmx_Form
     * @var Mmx_Form
     */
    protected $_form;

    /**
     * Modèle pour les données
     * @var Zend_Db_Table
     */
    protected $_model;

    /**
     * Messages d'erreur
     * @var $errorMsg
     */
    protected $_errorMsg;

    /**
     * Regles de validation
     * @var $validators
     */
    protected $_validators;

    /**
     * Regles de filtrage
     * @var $validators
     */
    protected $_filters;

    public function init()
    {

    }

    public function indexAction()
    {
        // Affiche un datagrid
    }
    

    public function addAction()
    {
        $this->_forward('edit');
    }

    /**
     * L'action edit gère à la fois l'insert et l'update
     *
     * Est ce que le formulaire a été envoyé ?
     * Oui -> formProcess : Non -> isIsert() ? oui -> formInit() : non -> formPopulate()
     */
    public function editAction()
    {
        // Créer une instance de formulaire
        Zend_Loader::loadClass('Mmx_Form');
        $this->_form = new Mmx_Form;

        // Dispatche
        if($this->getRequest()->isPost()){
            $this->_formProcess();
        } else {

            switch ($this->_isInsert()) {
                case true:
                    $this->_formInit();
                    break;
                     
                default:
                    $this->_formPopulate();
                    break;
            }

        }

        // Affiche le formulaire
        $this->view->form     = $this->_form;
        $this->view->errors   = $this->_errorMsg;
        $this->view->isInsert = $this->_isInsert();
    }

    public function deleteAction()
    {
        try{
            $this->_delete();
            // Done !
            $this->_formRedirect();
        }
        catch (Zend_Db_Exception $e){
            // TODO utiliser le flashMessager et faire un redirect
            $this->_errorMsg = "Il y a eu un probème lors de la suppression !";
            $this->_helper->viewRenderer('edit');
        }
         
    }
    
    abstract protected function _insert();

    abstract protected function _update();

    abstract protected function _delete();
    

    /**
     * Initialisation du formulaire lors d'une insertion
     */
    protected function _formInit();


    protected function _formPopulate()
    {
        $dbTable = $this->getModel();
        $id = (int)$this->_getPrimaryKeyValue();
        $row = $dbTable->find($id)->current();
        if($row){
            $data = $row->toArray();
            $this->_form->setDataSource($data);
        } else {
            $this->_errorMsg ="cet enregistrement n'existe pas" ;
        }
    }

    protected function _formProcess()
    {
        // si le formulaire est validé on traite les données
        if($this->_formValidate()) {
            $db = $this->getModel()->getAdapter();
            $db->beginTransaction();
            try{
                $this->_formSave();
                $db->commit();
                // Done !
                $this->_formRedirect();
            } catch (Zend_Db_Exception $e){
                $db->rollBack();
                $actionMsg = 'la mise à jour de l\'enregistrement';
                if($this->_isInsert()){
                    $actionMsg = 'l\'insertion de l\'enregistrement';
                }
                $this->_errorMsg = "Un problème avec la base de données est survenu lors de $actionMsg!";
                $appConfig = Zend_Registry::get('appConfig');
                if($appConfig->debug->displayexception){
                    $this->_errorMsg .= '<p>'.$e->getMessage().'</p>';
                }
            }
        }
    }

    /**
     * Valide et filtre les données envoyé via le formulaire
     *
     * @return boolean true si les données sont valides sinon false.
     */
    protected function _formValidate()
    {
        $this->_form->setDataSource($_POST);
        $this->_form->setValidationRules($this->_filters, $this->_validators);
        if ($this->_form->hasInvalid() || $this->_form->hasMissing()) {
            $this->_errorMsg = "le formulaire comporte des erreurs";
            return false;
        }else{
            return true;
        }
    }

    protected function _formSave()
    {
        if($this->_isInsert()){
            $this->_insert();
        } else {
            $this->_update();
        }
    }

    public function getModel()
    {
        return $this->_model;
    }

    public function setModel($model)
    {
        $this->_model = $model;
    }

    protected function _getPrimaryKeyValue()
    {
        return $this->_getParam($this->_paramPkName);
    }

    protected function _setPrimaryKeyName($name)
    {
        $this->_paramPkName = $name;
        // Type de la transaction UPDATE ?
        if($this->_hasParam($this->_paramPkName)){
            $this->_isInsertAction = false;
        }
    }

    protected function _getPrimaryKeyName()
    {
        return $this->_paramPkName;
    }

    protected function _getPrimaryKey()
    {
        return array('name'=>$this->_paramPkName, 'value'=>$this->_getPrimaryKeyValue());
    }


    protected function _isInsert()
    {
        return $this->_isInsertAction;
    }


    protected function _getWhereClause($pkType = 'int')
    {
        $pk = $this->_getPrimaryKey();

        switch ($pkType) {
            case 'string':
                return $this->getModel()->getAdapter()->quoteInto($pk['name'], $pk['value']);
                break;
                 
            default:
                return $pk['name'] .' = '.(int)$pk['value'];
                break;
        }
    }

    protected function _formRedirect()
    {
        // On retourne sur l'index
        // TODO faire un redirect sur la page par laquelle on est venu
        // il faudra préalablement la sauver dans une session
        $this->_helper->getHelper('Redirector')->goto('index');
    }

}

Exemple d'utilisation avec une table d'utilisateurs.

Pour le crudCrontroller


Code:

Zend_Loader::loadClass('Mmx_Controller_Crud');

class Users_CrudController extends Mmx_Controller_Crud
{
    public function init()
    {
        // Clès primaire
        $this->_setPrimaryKeyName('user_id');

        // Régles de validation des données
        $this->_validators = array(
        'username' => array('NotEmpty','presence' => 'required', 'messages' => 'Le champ est requis'),
        'password' => array('NotEmpty','presence' => 'required', 'messages' => 'Le champ est requis'),    
        'email' => array('EmailAddress', 'presence' => 'required', 'messages' => 'L\'adresse email n\'est pas valide')
        );

        $this->_filters = array(
        '*'     => 'StripTags' 
        );

        // Model
        Zend_Loader::loadClass('Users', Mmx_App::modulesPath().'/users/models');
        $userTable = new Users();
        $this->setModel($userTable);
    }

    public function indexAction()
    {
        // Code pour le data grid ici
    }

    protected function _formInit()
    {
        // pour l'exemple
        $this->_form->email  = 'votre email';
    }

    protected function _insert()
    {
        $this->_form->created  = Mmx_Form::getDate();
        $this->_form->modified = Mmx_Form::getDate();
        $data = $this->_form->getData(array('username', 'password', 'email', 'firstname', 'lastname', 'created', 'modified'));
        $this->getModel()->insert($data);
    }

    protected function _update()
    {
        $this->_form->modified = Mmx_Form::getDate();
        $data = $this->_form->getData(array('username', 'password', 'email', 'firstname', 'lastname', 'modified'));
        $this->getModel()->update($data, $this->_getWhereClause());
    }

    protected function _delete()
    {
        $this->getModel()->delete( $this->_getWhereClause() );
    }


}

Pour la vue edit.phtml

Code:

<?php echo $this->formErrors($this->errors); ?>

<form class="clearfix"  method="post" id="form" name="form">
        
        <p>
            <label for="username"><strong>Nom d'utilisateur :</strong></label>
            <?php echo $this->formText('username', $this->form->username, array('class'=>'text'))?> 
            <?php echo $this->formFieldErrors($this->form, 'username') ?>
        </p>
        
        <p>
            <label for="password"><strong>Mot de passe :</strong> </label>
            <?php echo $this->formText('password', $this->form->password, array('class'=>'text'))?>
            <?php echo $this->formFieldErrors($this->form, 'password') ?>
        </p>
        
        <p>
            <label for="email"><strong>Email :</strong></label>
            <?php echo $this->formText('email', $this->form->email, array('class'=>'text'))?>
            <?php echo $this->formFieldErrors($this->form, 'email') ?>
        </p>
        
        <p>
            <label for="firstname">Prénom :</label>
            <?php echo $this->formText('firstname', $this->form->firstname, array('class'=>'text'))?> 
        </p>
        
        <p>
            <label for="lastname">Nom :</label>
            <?php echo $this->formText('lastname', $this->form->lastname, array('class'=>'text'))?> 
       </p>
        
        
        <p>
            <button type="submit" class="submit" name="submit" value="Envoyer">Envoyer</button>
        </p>
        
    </form>

Voilà ce n'est qu'un début de réflexion on peut encore apporter beaucoup d'améliorations, je vais continuer à expérimenter avec des données plus complexes pour en trouver les limites.

On peut aussi envisager d'aller encore plus loin avec un système complètement automatisé (en utilisant les metadatas des tables) pour réaliser des prototypes. 

listing + crud en 10 – 20 lignes de code smile

Code:

class CrudController extends  Mmx_Controller_Crud_Omatic{

    public function Init()
    {
        $model = new MyTable();
        $this->setModel($model);
        $this->useDataForGrid(array(...));
        $this->useDataForInsert(array(...));
        $this->useDataForUpdate(array(...));
    }
}

dans index.phtml

<php echo $this->gridOmatic($this->grid) ?>

puis dans edit.phtml

<php echo $this->formOmatic($this->from) ?>


Voire, pas de vue du tout si l'on utilise le Response Object.


A suivre ...

Dernière modification par 2mx (23-11-2007 17:27:54)

Hors ligne

 

#2 23-11-2007 17:18:57

sekaijin
Membre
Date d'inscription: 17-08-2007
Messages: 1137

Re: CRUD best practice ?

pour ma part je fait mes controlleur de crud par simple dérivation de cette classe

Code:

<?php
require_once dirname(__FILE__).'/Action.php';
Zend_Loader::loadClass('Fast_Exception');
Zend_Loader::loadClass('Fast_Collectionable_Interface');

/**
* CRUD is that many editing tasks in PHP are redundant, since they basically do the same thing:
* Create new Records
* Retrieve those records via some search interface, displaying results in a hitlist format
* Update existing records, using a form similar to the create phase
* Delete some records
*
* @author Jean-Yves Terrien
*
*/

abstract class Fast_Controller_Crud extends Fast_Controller_Action
{
   protected $_getList = null;
   protected $_getAlphabeticCount = null;
   protected $_newItem = null;
   protected $_getItemById = null;
   protected $_messages = null;
   protected $_titles = null;
   protected $_buttons = null;
   protected $_alphabeticField = null;

   public function init() { 
      if (null == $this->_getList)            throw new Fast_Exception('$getList non défini');
      if (null == $this->_getAlphabeticCount) throw new Fast_Exception('$getAlphabeticCount non défini');
      if (null == $this->_newItem)            throw new Fast_Exception('$newItem non défini');
      if (null == $this->_getItemById)        throw new Fast_Exception('$getItemById non défini');
      if (null == $this->_messages)           throw new Fast_Exception('$messages non défini');
      if (null == $this->_titles)             throw new Fast_Exception('$titles non défini');
      if (null == $this->_buttons)            throw new Fast_Exception('$_buttons non défini');
      if (null == $this->_alphabeticField)    throw new Fast_Exception('$_alphabeticField non défini');
      parent::init();
      $this->view->crud = new StdClass();
   }

   public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array()) {
      parent::__construct($request, $response, $invokeArgs);
   }

   /**
    * Action par défaut redirige vers showList
    * La redirection est effective si $perform est vrai.
    * Cette action n'est pas plac dans l'historique.
    * Une classe héritière peut remplacer cette méthode.
    * Pour la surcharger tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false
    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *
    * @param boolean $perform
    * @return null
    * @see showList
    */
   public function indexAction($perform = true) {
      $this->_popHistory();
      $redirect = $this->_to('showList');
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * Affiche la liste des éléments avec un index alphabetique
    *
    * @return null
    */
   public function showListAction(){
      $this->_createContext();
      $baseAction = $this->view->baseUrl.'/'.$this->view->module.'/'.$this->view->controller;
      $this->view->crud->listAction   = $baseAction.'/showList/';
      $this->view->crud->addAction    = $baseAction.'/add/';
      $this->view->crud->editAction   = $baseAction.'/edit/';
      $this->view->crud->deleteAction = $baseAction.'/delete/';

      $this->view->crud->addButton    = $this->_buttons['add'];
      $this->view->crud->deleteButton = $this->_buttons['delete'];
      $this->view->crud->returnButton = $this->_buttons['return'];

      $this->view->page->title = $this->_titles['showList'];

      if (isset($this->history->previous->path)) 
         $this->view->crud->returnPath = $this->history->previous->path;
      $alphabet = $this-> _getAlphabet();
      // place la lettre choisie dans le contexte si besoin
      if ($nameStart = $this->_request->get("nameStart")) {
         $this->context->nameStart = $nameStart;
      } elseif (!isset($this->context->nameStart)) {
         $this->context->nameStart = $alphabet->first;
      }

      $items = $this->model->{$this->_getList}($this->context->nameStart);
      if (0 == count($items)) {
         $this->_messenger->addNotice($this->_messages['noItemFor']. $this->context->nameStart);
      } else {
         $list = array();
         foreach ($items as $data) {
            #$this->_toFrenchDate($data);
            $list[] = $data;
         }
         $this->view->list = $list;
      }

      $this->view->alphabet =  $alphabet->letters;
   }


   /**
    * Prépare un enregistrement pour l'afficher dans le formulaire
    * redirige vers showForm     
    * la redirection est effective si $perform est vrai.
    * Pour surcharger cette méthode tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false
    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *        
    * @param boolean $perform
    * @return null
    * @see showForm    
    */
   public function addAction($perform = true) {
      $this->_createContext();
      $this->context->returnPath = $this->history->previous->path;
      $this->context->saveMethod = 'add';
      $data = $this->model->{$this->_newItem}();
      if (! $data instanceof Fast_Collectionable_Interface)
         throw new Fast_Exception(get_class($data).' n\'implémente pas l\'interface : Fast_Collectionable_Interface');
      $this->context->formData = $data->toStdClass();
      $this->context->formErrors = new StdClass(); //on n'a à priori pas d'erreurs
      $redirect = $this->_to('showForm');
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * Recherche l'enregistrement pour l'afficher dans le formulaire
    * redirige vers showForm     
    * la redirection est effective si $perform est vrai.
    * Pour surcharger cette méthode tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *        
    * @param boolean $perform
    * @return null
    * @see showForm    
    */
   public function editAction($perform = true) {
      $this->_createContext();
      $this->context->returnPath = $this->history->previous->path;
      $this->context->saveMethod = 'update';

      $id = $this->_request->get('id');      $data = $this->model->{$this->_getItemById}($id);
      if (!$data) {
         // revenir au point de retour
         $this->_messenger->addError($this->_messages['unknowItem'] . $id);
         $redirect = $this->context->returnPath;
      } else {
         if (! $data instanceof Fast_Collectionable_Interface)
            throw new Fast_Exception(get_class($data).' n\'implémente pas l\'interface : Fast_Collectionable_Interface');
         $this->context->formData = $data->toStdClass();
         $this->context->formErrors = new StdClass(); //on n'a à priori pas d'erreurs
         $redirect = $this->_to('showForm');
      }
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * Recherche l'enregistrement à supprimer pour confirmation
    * redirige vers confirm     
    * la redirection est effective si $perform est vrai.
    * Pour surcharger cette méthode tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false
    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *        
    * @param boolean $perform
    * @return null
    * @see confirm    
    */
   public function deleteAction($perform = true) {
      $this->_createContext();
      $this->context->returnPath = $this->history->previous->path;

      $id = $this->_request->get("id");
      $data = $this->model->{$this->_getItemById}($id);
      if (!$data) {
         // revenir au point de retour
         $this->_messenger->addError($this->_messages['unknowItem'] . $id);
         $redirect = $this->context->returnPath;
      } else {
         if (! $data instanceof Fast_Collectionable_Interface)
            throw new Fast_Exception(get_class($data).' n\'implémente pas l\'interface : Fast_Collectionable_Interface');
         $this->context->formData = $data->toStdClass();
         $redirect = $this->_to('confirm');
      }
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * Affiche une demande de comfirmation de suppression d'un élément.
    *
    * @return null
    */
   public function confirmAction() {
      $this->_linkContext();
      if (isset($this->context->formData)) {
         $baseAction = $this->view->baseUrl.'/'.$this->view->module.'/'.$this->view->controller;
   
         $this->view->form = clone($this->context->formData);
         $this->view->page->title = $this->_titles['deleteConfirm'];
         $this->view->crud->cancelAction = $this->view->baseUrl.$this->context->returnPath;
         $this->view->crud->deleteAction = $baseAction.'/remove/';
   
         $this->view->crud->cancelButton = $this->_buttons['cancel'];
         $this->view->crud->deleteButton = $this->_buttons['delete'];
      } else {
         trigger_error('Invalid context to call confirmAction', E_USER_WARNING);
         $redirect = $this->history->previous->path;
         $this->_deleteContext($this->_popHistory());
         $this->_redirect($redirect);
      }      
   }
  
   /**
    * Affiche le formulaire d'édition d'un élément.
    *
    * @return null
    */
   public function showFormAction(){
      $this->_linkContext();
      if (isset($this->context->saveMethod)) {
         $baseAction = $this->view->baseUrl.'/'.$this->view->module.'/'.$this->view->controller;
         $this->view->page->title = $this->_titles[$this->context->saveMethod];
         $this->view->crud->cancelAction = $this->view->baseUrl.$this->context->returnPath;
         $this->view->crud->saveAction = $baseAction.'/checkForm/';   
         $this->view->crud->cancelButton = $this->_buttons['cancel'];
         $this->view->crud->saveButton   = $this->_buttons['save'];         
         $this->view->form = clone($this->context->formData);
         $this->view->formErrors = clone($this->context->formErrors);
      } else {
         trigger_error('Invalid context to call showFormAction', E_USER_WARNING);
         $redirect = $this->history->previous->path;
         $this->_deleteContext($this->_popHistory());
         $this->_redirect($redirect);
      }      
   }

   /**
    * Récupère les données du formulaire les filtres et les vérifie
    * la méthode _verifForm doit être fournie par la classe héritière.    
    * redirige vers save si le formulaire est valide showFrom sinon     
    * la redirection est effective si $perform est vrai.
    * Pour surcharger cette méthode tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false
    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *        
    * @param boolean $perform
    * @return null
    * @see save    
    * @see showForm    
    */
   public function checkFormAction($perform = true) {
      $this->_linkContext();

      if ($form = $this->_request->get('form')) 
      if (method_exists($this, '_filter')) {
         $this->_filter($form);
      } else {
         foreach ($form as $key=>$value) {
            $this->context->formData->$key = $value;
         }
      }

      $ok = $this->_verifForm();
      if ($ok) {
         $redirect = $this->_to('save');
      } else {
         $this->_messenger->addError($this->_messages['invalidFormData']);
         $redirect = $this->_to('showForm');
      }
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * Supprime l'élément de la collection
    * redirige vers l'action qui précédé l'actio delete     
    * la redirection est effective si $perform est vrai.
    * Pour surcharger cette méthode tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false
    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *        
    * @param boolean $perform
    * @return null
    * @see delete   
    */
   public function removeAction($perform = true) {
      $this->_linkContext();

      $id = $this->_request->get("id");

      $data = $this->model->{$this->_getItemById}($id);
      if (!$data) {
         // revenir au point de retour
         $this->_messenger->addError($this->_messages['unknowItem'] . $id);
      } else {
         $ok = $this->model->{$this->_deleteItemById}($id);
         if ($ok) {
            $this->_messenger->addOk($this->_messages['deleted']);
         } else {
            $this->_messenger->addError($this->_messages['undeleted']);
         }
      }
      $redirect = $this->context->returnPath;
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * enregistre l'élément dans la collection
    * redirige vers l'action qui précédé l'actio add ou edit en cas de success
    * vers showForm en cas d'échec    
    * la redirection est effective si $perform est vrai.
    * Pour surcharger cette méthode tout en gardant sont fonctionnement 
    * appeler la méthode parente avec $perform à false
    * ajouter les traitements désiré puis appeler 
    * $this->_redirect($redirect);
    *        
    * @param boolean $perform
    * @return null
    * @see add
    * @see edit
    * @see showForm    
    */
   public function saveAction($perform = true) {
      $this->_linkContext();
      
      $data = $this->model->{$this->_newItem}($this->context->formData);
      $method = $this->context->saveMethod;

      if (! $data instanceof Fast_Collectionable_Interface)
         throw new Fast_Exception(get_class($data).' n\'implémente pas l\'interface : Fast_Collectionable_Interface');      
      $ok = $data->$method();
      if ($ok) {
         $this->_messenger->addOk($this->_messages['saved']);
         if ('add' == $method) {
            $data = $this->model->{$this->_getItemById}($ok);
            $this->context->formData = $data->toStdClass();
            $this->context->saveMethod = 'update';
         } 
         // on mets à jour la lettre de la liste
         $showList = $this->_getHistory($this->context->returnPath);
         if ($showList) {
            $showListContext = self::_getContext($showList->context);
            $showListContext->nameStart = substr($this->context->formData->{$this->_alphabeticField}, 0, 1);
         }
         $redirect = $this->context->returnPath;
      } else {
         $this->_messenger->addError($this->_messages['unsaved']);
         $redirect = $this->_to('showForm');
      }
      if ($perform) $this->_redirect($redirect);
      else return $redirect;
   }

   /**
    * Retourne un objet standard décrivant alphabétiquement
    * le nombre d'enregistrement d'une table en fonction de
    * l'initiale d'un champ.
    *
    * @return stdClass
    */
   protected function _getAlphabet()
   {
      $alphabet = new StdClass();
      $alphabet->letters = $this->model->{$this->_getAlphabeticCount}();;
      $total = 0;
      $first = null;
      if ($alphabet->letters) {
         foreach ($alphabet->letters as $key=>$value) {
            $total += $value->nb;
            if (is_null($first) && $value->nb > 0) $first = $value->letter;
         }
         // première lettre qui contient au moins un nom
         $alphabet->first = $first;
         // nombre total de noms
         $alphabet->total = $total;
      }
      if (is_null($first))
         $alphabet->first = 'A';
      return $alphabet;
   }
   
   /**
    * Recupère toutes les erreurs de vérification
    * ajoutes les messages dans le messager et les erreurs dans le contexte.
    *
    * @return boolean validité du formulaire
    */

   protected function _performVerif(Fast_Validate $validate) {
      $ok = $validate->isValid();
      if (!$ok) {
         foreach ($validate->getMessages() as $msg) {
            $this->_messenger->addError($msg);
         }
         $this->context->formErrors = $validate->getErrors();
      } 
      return $ok;
   }
}

de cette façon

Code:

<?php
require dirname(__FILE__).'/Action.php';

/**
* CRUD for Group
*
* @author Jean-Yves Terrien
*
*/
class Adm_GroupController extends Adm_Action
{
   protected $_getList            = 'getAlphabeticGroupList';
   protected $_getAlphabeticCount = 'getGroupAlphabetic';
   protected $_newItem            = 'newGroup';
   protected $_getItemById        = 'getGroupById';
   protected $_deleteItemById     = 'deleteGroupById';
   protected $_messages = array(
      'noItemFor'       => 'Aucun élément pour l\'initiale : ',
      'unknowItem'      => 'enregistrement introuvable : ',
      'invalidFormData' => 'Les données du formulaire sont invalides',      
      'saved'           => 'Enregistrement effectué',
      'unsaved'         => 'Impossible d\'enregistrer',
      'deleted'         => 'Groupe définitivement supprimé',
      'undeleted'       => 'Impossible de supprimer le groupe'
   );
   protected $_titles = array(
      'showList'      => 'Gestion des groupes',
      'deleteConfirm' => 'Suppression du groupe',
      'add'           => 'Création d\'un groupe',
      'update'        => 'Mise à jour d\'un groupe'
   );
   protected $_buttons =  array(
      'cancel' => 'Retour',
      'save'   => 'Enregistrer',
      'add'    => 'Ajouter un groupe',
      'delete' => 'Supprimer',
      'return' => 'Retour',
   );
   protected $_alphabeticField = 'wkg_label';

   public function init()
   {
      parent::init();
      $this->model->addComponent('Adm_Model_Group' , dirname(dirname(__FILE__)).'/Model');
   }
  
   public function showFormAction(){
      parent::showFormAction();
      $items = $this->model->{$this->_getList}();
      $list = array();
      foreach ($items as $data) {
        if (isset($this->context->formData->parent_id) && ($data->id == $this->context->formData->parent_id)) {
         $data->selected = true;
        }      
        $list[] = $data;
      }
      $this->view->list = $list;
      $items = $this->model->getGroupTypes();
      $list = array();
      foreach ($items as $data) {
        if (isset($this->context->formData->wgt_id) && ($data->id == $this->context->formData->wgt_id)) {
         $data->selected = true;
        }      
        $list[] = $data;
      }
      $this->view->types = $list;
   }

   protected function _verifForm() {
      Zend_Loader::loadClass('Fast_Validate');

      $verifForm = array(
         'parent_id'      => array('required' => "le parent est obligatoire"),
         'wkg_code'       => array('required' => "le code est obligatoire"),
         'wkg_label'      => array('required' => "le libellé est obligatoire"),
         'wkg_shortlabel' => array('required' => "le libellé court est obligatoire"),
         'wkg_valid'      => array('required' => "La validité est obligatoire"),
         'wgt_id'         => array('required' => "le type est obligatoire")
      );
      $validate = new Fast_Validate($this->context->formData, $verifForm);
      return parent::_performVerif($validate);
   }

   protected function _filter($form) {
      Zend_Loader::loadClass('Zend_Filter');
      Zend_Loader::loadClass('Zend_Filter_StripTags');
      Zend_Loader::loadClass('Zend_Filter_StringTrim');
      $filter = new Zend_Filter();
      $filter->addFilter(new Zend_Filter_StripTags());
      $filter->addFilter(new Zend_Filter_StringTrim());
      foreach ($form as $key=>$value) {
         $this->context->formData->$key = $filter->filter($value);
      }
   }
}

j'ai un peu amélioré le truc depui mais je n'ais pas la version sur cette machine

A+JYT
PS : je mettrais un package complet et à jour sur mon blog dès que j'aurais rédigé l'article

Hors ligne

 

#3 23-11-2007 17:34:51

2mx
Membre
Lieu: Grenoble
Date d'inscription: 06-08-2007
Messages: 125

Re: CRUD best practice ?

Cool sekaijin,

je vais jeter un œil a ton code en début de semaine prochaine, en attendant de te lire sur ton blog smile

@+

Hors ligne

 

#4 23-11-2007 20:36:17

Julien
Membre
Date d'inscription: 16-03-2007
Messages: 501

Re: CRUD best practice ?

Très intérressant, merci pour vos partages respectifs !!
Nul doute que d'ici quelques temps, un système du genre sera intégré au ZendFramework (c'est sûr, car c'est tellement utilisé ...).
Nous attendons Zend_Form, et Zend_layout ; puis d'autres propositions sont ou serons faites.

Zend_Layout est actuellement en incubateur (vous pouvez donc le tester sans problème, via le dépot svn), il sera prochainement intégré (aucune date précise en revanche).
Il y a aussi possibilité d'intégrer le CRUD dans la partie contrôleur (comme vous faites), c'est passé en mailing list, et déja quelques helper, niveau controleur d'action(Helper), ou frontcontrôleur(Plugin sur pré et post dispatch) commencent à voir le jour.
ZF est encore un peu jeune sur ce point là ( bien que stable ), mais les règles sont claires sur ce projet : il faut que les composants soient utiles, développés de manière intelligente avec possibilité de les étendre facilement, afin qu'on ne soit pas "bloqués", testés (PHPUnit est l'outil de test), et "dans l'esprit PHP".

Notez : ZF est fait pour être "développable", et utilisable de manière passive, comme active (dérivation; comme vous faites); c'est ce qui fait sa force, et ce que j'aime personnellement dans ce projet.
Bravo à vous ^^ (mais aussi aux autres ici smile)

Hors ligne

 

#5 25-11-2007 09:37:05

2mx
Membre
Lieu: Grenoble
Date d'inscription: 06-08-2007
Messages: 125

Re: CRUD best practice ?

Salut Julien,

J'ai bien conscience qu'un CRUD serra intégré au ZF mais là je vais démarrer mon 1er gros projet avec ZF  et il me faut une solution tout de suite; je ne peux plus attendre.

J'espérais pouvoir démarrer avec Zend_Form mais là ça va être trop juste (Matthew n'a pas encore posé sa proposition sur le wiki). Je viens d'accrocher Zend_Layout au passage et c'est déjà pas mal. J'ai aussi intercepté dans la mailing que des outils de génération de code étaient en cours de discussion. Mais à vue d'oeil, comme ça, je ne pense pas que tout ces outils seront bien dispo avant le printemps... Et là mon projet serra fini. smile

Mais ne me mèprenez pas, ça ne veux pas dire que je suis impatient (dans le mauvais sans du terme) je préfère me dépatouiller et que le Team ZF prennent leur temps pour continuer à faire de la qualité, le jeu en vaut largement la chandelle ! Une fois que tout ça sera calé ZF va vraiment monter en puissance et ça va faire mal ! smile

En attendant et je pense que c'est le cas de tout le monde il faut trouver des solutions d'attente.


@+

Dernière modification par 2mx (25-11-2007 09:40:05)

Hors ligne

 

#6 25-11-2007 09:39:37

2mx
Membre
Lieu: Grenoble
Date d'inscription: 06-08-2007
Messages: 125

Re: CRUD best practice ?

Julien a écrit:

Il y a aussi possibilité d'intégrer le CRUD dans la partie contrôleur (comme vous faites), c'est passé en mailing list, et déja quelques helper, niveau controleur d'action(Helper), ou frontcontrôleur(Plugin sur pré et post dispatch) commencent à voir le jour.

Tu aurais plus d'infos là dessus ?

Hors ligne

 

#7 11-04-2009 07:59:07

Harry
Membre
Lieu: Paris
Date d'inscription: 01-04-2009
Messages: 12

Re: CRUD best practice ?

Bonjour,
J'ai un petit problème concernant l'utilisation d'un tel CRUD (celui de Sekaijin) au cas où l'on choisirait un type de liste différent selon la valeur d'un paramètre :
par exemple si $this->_request('actif') == true
on voudrait avoir : protected $_getList            = 'getActifGroupList';
mais si $this->_request('actif') == false
on voudrait avoir : protected $_getList            = 'getInactifGroupList';
Le CRUD générique est alors plus délicat à utiliser.
J'ai pensé, dans la méthode showForm à faire :
function showFormAction{
if ($this->_request('actif') == true) $_getList = 'getActifGroupList';
if ($this->_request('actif') == false) $_getList = 'getInactifGroupList';
parent::showForm();

Qu'en pensez-vous ? C'est un peu lourd non ?

Hors ligne

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages