Добрый день коллеги, сегодня я расскажу сказку о своём опыте организации контроллеров в проекте на ZF 1 (так исторически сложилось).
В хороших книжках по ООП часто пишут, что наследованием нельзя увлекаться, нужно предпочитать делегирование или делать так, чтобы они работали совместно. К сожалению, не всегда можно быстро догадаться, как применить сухую теорию на практике (а когда наконец-то доходит, удивляешься «что тут сложного?»), поэтому надеюсь мой опыт кому-нибудь пригодится.
И так сначала о проблемной области:
31 Controller Action, большинство из них имеет методы indexAction(), addAction(), editAction(), searchAction().
проблема №1: большинство, но не все. В остальных наличие этих методов варьируется,
проблема №2: методы editAction() и addAction() массивные сами по себе, и почти одинаковые для всех контроллеров, отличаются инициализация формы, и сохранение модели.
Как я это решил, покажу сразу в коде.
Фрагмент базового класса контроллеров
Protected метод modelFactory() создает экземпляр модели, связанной с конкретным контроллером. Класс модели указывается в переменной $this->modelClass и в большинстве случаев, кастомизация на этом заканчивается. Если модель должна быть инициализирована по особенному, то просто переопределяем этот метод в конкретном контроллере.
Protected метод formFactory() создает форму редактирования, кастомизация аналогично modelFactory().
Protected метод save() сохраняет в переданную модель данные из переданной формы, здесь также есть место для маневра, если в конкретном контроллере сохранение сущности получается развесистым. Наличие такого метода в контроллере может вызвать сомнение, поэтому поясню, в save() допускаются только вызовы дополнительных методов модели, никаких sql запросов нет, для этого в модели мы определяем методы вроде addTag(), setChannles() и т.п. вместо одного непрозрачного метода saveFromArray().
При такой композиции, модель и форма ничего не знают друг о друге, а контроллер играет роль интегратора.
Последний protected метод в этом фрагменте это _editActionHelper(), если в производном классе нам понадобится поддержка редактирования сущности, то мы просто добавляем в него метод:
Аналогично для других общих методов. Фрагмент производного контроллера для примера:
P.S. просьба, если что-то ни так, пишите в комментарии, это будет полезно всем читателям.
В хороших книжках по ООП часто пишут, что наследованием нельзя увлекаться, нужно предпочитать делегирование или делать так, чтобы они работали совместно. К сожалению, не всегда можно быстро догадаться, как применить сухую теорию на практике (а когда наконец-то доходит, удивляешься «что тут сложного?»), поэтому надеюсь мой опыт кому-нибудь пригодится.
И так сначала о проблемной области:
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. просьба, если что-то ни так, пишите в комментарии, это будет полезно всем читателям.