Применяем делегирование совместно с наследованием для организации контроллеров действий

от автора

Добрый день коллеги, сегодня я расскажу сказку о своём опыте организации контроллеров в проекте на ZF 1 (так исторически сложилось).
В хороших книжках по ООП часто пишут, что наследованием нельзя увлекаться, нужно предпочитать делегирование или делать так, чтобы они работали совместно. К сожалению, не всегда можно быстро догадаться, как применить сухую теорию на практике (а когда наконец-то доходит, удивляешься «что тут сложного?»), поэтому надеюсь мой опыт кому-нибудь пригодится.

И так сначала о проблемной области:
31 Controller Action, большинство из них имеет методы indexAction(), addAction(), editAction(), searchAction().
проблема №1: большинство, но не все. В остальных наличие этих методов варьируется,
проблема №2: методы editAction() и addAction() массивные сами по себе, и почти одинаковые для всех контроллеров, отличаются инициализация формы, и сохранение модели.

Как я это решил, покажу сразу в коде.

Фрагмент базового класса контроллеров

Class Common_Controller extends Zend_Controller_Action {     /**     * Класс модели связанной с контроллером.     * @var string;     */     protected $modelClass = '';      /**     * Класс формы редактирования модели связанной с контроллером.     * @var string;     */     protected $editFormClass  = '';      /**     * JS файл, если он нужен, для редактирования модели связанной с контроллером.      * @var string;     */     protected $jsModelFile = '';      /**     * Создает модель связанную с контроллером,     * если $id передан - ищет в базе, если нет - создает новую.      *      * @param mixed $id;     * @return Model_Record $model;     */     protected function modelFactory( $id = null )     {         $modelClass = $this->modelClass;         if  ( $id )  {             $model = $modelClass::find( $id );             }         else {             $model = $modelClass::create();         }         return $model;     }          /**     * Создает форму редактирования связанную с контроллером.     *      * @param Model_Record $model;     * @return Zend_form $form;     */     protected function formFactory( Model_Record $model )     {         $formClass = $this->editFormClass;         $form = new $formClass();         $form->setDefaults( $model->toArray(1) );         return $form;     }      /**     * Заполняем модель данными из формы и сохраняем.      *      * @param Model_record $model;     * @param Zend_Form $form;     */     protected function save( Model_Record $model , Zend_Form $form )     {         $model->fromArray($form->getValues(), false);         $model->save();     }      /**     * Хелпер редактирования сущности,      * этому методу делегируются вызовы editAction() в производных контроллерах.     */     protected function _editActionHelper()     {         $id = $this->_request->getParam('id');         if ( !$id ) {             throw new Zend_Controller_Action_Exception('страница не найдена' , 404);          }         // модель поднимаем          $model = $this->modelFactory($id);                   if ( !$model ) {             throw new Zend_Controller_Action_Exception('страница не найдена' , 404);          }                  $this->view->model = $model;          // заголовок страницы         $this->view->PageTitle = $model->getFullTitle();                  // форму редактирования создаем         $form  = $this->formFactory( $model );         $this->view->form = $form;                  // если страница загружена get-ом - прокидываем дальше (через форму) реферер, куда вернуться после сохранения          if ( $this->_request->isGet() )  {             $form->redirect->setValue( $_SERVER['HTTP_REFERER']  );         }                         // проверяем, есть ли права на редактирование записи         // ...                 // блокируем запись         $model->lock();                  // js файл подключаем         if ( $this->jsModelFile ) {             $this->view->headScript()->appendFile( '/js/models/' . $this->jsModelFile );         }          // сохраняем данные         if ( isset($_POST['save']) || isset($_POST['saveExit']) ) {             // валидация             if (  $form->isValid( $this->_request->getPost() )  )  {                     // пробуем сохранить                  try {                     Model::connection()->beginTransaction();                      $this->save( $model, $form );                      Model::connection()->commit();                       $model->releaseLock();                      $this->view->Flash()->addSuccess( 'Success !' );                       // решаем, что делать после сохранения                      // вернуться на эту же страницу                     $redirect = '/' . $this->_request->getControllerName() . '/edit/id/' . $model->ID                               . '?redirect=' . $this->_request->getParam('redirect', '/');                      // сохранить и выйти                      if ( isset($_POST['saveExit']) ) {                           $redirect = $this->_request->getParam('redirect', '/');                      }                      $this->_redirect( $redirect );                 }                 catch (Exception $e) {                          Model::connection()->rollback();                     $this->view->Flash()->addError( $e->getMessage() );                 }             }             else {                 $this->view->Flash()->addError("Форма заполнена с ошибками");             }         }     } } 

Protected метод modelFactory() создает экземпляр модели, связанной с конкретным контроллером. Класс модели указывается в переменной $this->modelClass и в большинстве случаев, кастомизация на этом заканчивается. Если модель должна быть инициализирована по особенному, то просто переопределяем этот метод в конкретном контроллере.

Protected метод formFactory() создает форму редактирования, кастомизация аналогично modelFactory().

Protected метод save() сохраняет в переданную модель данные из переданной формы, здесь также есть место для маневра, если в конкретном контроллере сохранение сущности получается развесистым. Наличие такого метода в контроллере может вызвать сомнение, поэтому поясню, в save() допускаются только вызовы дополнительных методов модели, никаких sql запросов нет, для этого в модели мы определяем методы вроде addTag(), setChannles() и т.п. вместо одного непрозрачного метода saveFromArray().
При такой композиции, модель и форма ничего не знают друг о друге, а контроллер играет роль интегратора.

Последний protected метод в этом фрагменте это _editActionHelper(), если в производном классе нам понадобится поддержка редактирования сущности, то мы просто добавляем в него метод:

    public function editAction()     {          $this->_editActionHelper();     } 

Аналогично для других общих методов. Фрагмент производного контроллера для примера:

Class Video extends Common_Controller {     protected $modelClass = 'Video';     protected $editFormClass  = 'Form_Video';          protected function save( Model_Record $model , Zend_Form $form )     {            parent::save(  $model ,   $form );            $model->setChannels( $form->channels->getValue() );     }            public function editAction()     {            $this->_editActionHelper();     }          public function addAction()     {             $this->_addActionHelper();     }          public function indexAction()     {            $this->_indexActionHelper();     } } 

P.S. просьба, если что-то ни так, пишите в комментарии, это будет полезно всем читателям.

ссылка на оригинал статьи http://habrahabr.ru/post/213971/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *