Наследование CRUD операций от базового контроллера
Раньше мы рассматривали возможность повторного использования стандартных CRUD операций путём выноса их в отдельные классы (наследники класса CAction
в Yii). У этого способа есть альтернатива – наследование общих действий от базового контроллера. Рассмотрим этот способ подробнее, а также попробуем найти и решить некоторые его проблемы.
Использования наследования – простой способ, но изначально (как и сами наследование) не очень гибкий.
Итак, можно переместить стандартные действия в базовый контроллер, чтобы потом наследовать эти действия от него:
class Controller extends CController { public function actionIndex(){...} public function actionAdmin(){...} public function actionCreate(){...} public function actionUpdate(){...} public function actionDelete(){...} public function actionView(){...} } class PostAdminController extends Controller {}
При таком наследовании в контроллер PostAdminController
(и во все остальные) автоматически добавятся стандартные действия из родительского контроллера. Если нужно изменить какое либо действие, то его необходимо переопределить в дочернем классе PostAdminController
.
Но не всем контроллерам эти действия нужны. Чтобы сделать эти действия доступными только для контроллеров панели управления можно их описать в промежуточном классе AdminController
:
class Controller extends CController {} class PostController extends Controller {} class AdminController extends Controller { public function actionIndex(){...} public function actionAdmin(){...} public function actionCreate(){...} public function actionUpdate(){...} public function actionDelete(){...} public function actionView(){...} } class PostAdminController extends AdminController {}
но это не даст использовать полезные действия actionIndex
и actionView
в контроллерах вне админки.
В обоих случаях имеется проблема с тем, что из за статической природы наследования все действия подключаются сразу. Нельзя выбрать из них только нужные. Это приведёт к тому, что придётся переопределять все ненужные методы. Например, чтобы использовать только два метода actionAdmin
и actionUpdate
нам нужно эмулировать отсутствие четырёх других:
class Controller extends CController { public function actionIndex(){...} public function actionAdmin(){...} public function actionCreate(){...} public function actionUpdate(){...} public function actionDelete(){...} public function actionView(){...} } class ConfigAdminController extends Controller { public function actionIndex() { throw new CHttpException(404, 'Not found'); } public function actionCreate() { throw new CHttpException(404, 'Not found'); } public function actionDelete($id) { throw new CHttpException(404, 'Not found'); } public function actionView($id) { throw new CHttpException(404, 'Not found'); } }
Для решения этой проблемы можно вести список нужных экшенов crudActions()
и осуществлять проверку в каждом действии:
class Controller extends CController { public function crudActions() { return array(); } public function actionIndex() { $this->checkCrudAction(); // ... } public function actionAdmin() { $this->checkCrudAction(); // ... } public function actionCreate() { $this->checkCrudAction(); // ... } public function actionUpdate() { $this->checkCrudAction(); // ... } public function actionDelete() { $this->checkCrudAction(); // ... } public function actionView() { $this->checkCrudAction(); // ... } protected function checkCrudAction() { if (!in_array($this->action->id, $this->crudActions()) throw new CHttpException(404, 'Not found'); } }
В дочерних контроллерах теперь можно переопределить список необходимых им методов:
class ConfigAdminController extends Controller { public function crudActions() { return array( 'admin', 'update', ); } }
Каждое действие перед своей работой вызывает проверку на вхождение себя в список указанных в методе crudActions()
.
Чтобы не загружать базовый контроллер лишними проверками целесообразно вынести их в отдельный фильтр:
class CrudActionFilter extends CFilter { public $actions = array( 'index', 'admin', 'create', 'update', 'delete', 'view', ); protected function preFilter($filterChain) { $controller = $filterChain->controller; $action = $controller->action; if (in_array($action->id, $this->actions) && !in_array($action->id, $controller->crudActions()) throw new CHttpException(404, 'Not Found'); return true; } }
и подключить этот фильтр к базовому контроллеру:
class Controller extends CController { public function filters() { return array( array( 'CrudActionFilter', ) ); } public function crudActions() { return array(); } public function actionIndex(){...} public function actionAdmin(){...} public function actionCreate(){...} public function actionUpdate(){...} public function actionDelete(){...} public function actionView(){...} }
Дочерние контроллеры останутся без изменений:
class ConfigAdminController extends Controller { public function crudActions() { return array( 'admin', 'update', ); } }
Это теперь стало похоже на подключение действий, оформленных в виде классов из рассмотренного ранее способа выноса действий в отдельные классы:
class ConfigAdminController extends Controller { public function actions() { return array( 'admin'=>'DAdminAction', 'update'=>'DUpdateAction', ); } }
но не поддерживает переименования действий.
Дальше можно пофантазировать и построить действия так, чтобы их можно было конфигурировать через метод crudActions()
. Например так:
class ConfigAdminController extends Controller { public function crudActions() { return array( 'admin'=>array('view'=>'index', 'ajaxView'=>'_grid'), 'update', ); } }
или так:
class ConfigAdminController extends Controller { public function crudActions() { return array( array('admin', 'view'=>'index', 'ajaxView'=>'_grid'), 'update', ); } }
И дополнить базовый контроллер методом getActionParam($action, $param)
для извлечения параметров из этого массива. Не забудьте при этом переделать проверки в фильтре CrudActionFilter
под новую структуру.
:-) Бритва Оккама.
В случае админки отдельным модулем или приложением вполне достаточно
Достаточно, но по пять таких заглушек подряд (как в третьем листинге) в некоторых местах будут смотреться весьма странно.
относительно недавно пришел к такоку аля generic-контроллеру, указываем классы которые нам помогут и готово