Наследование CRUD операций от базового контроллера

Controller

Раньше мы рассматривали возможность повторного использования стандартных 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 под новую структуру.

Другие статьи

В системах управления контентом (CMS) интернет-магазинов (да и часто для других нужд) можно встретить возможность добавлять неограниченное число полей к различным сущностям. Это знакомые всем списки характеристик товаров, а иногда имеется целая система создания новых типов контента (CCK), которой, кстати, славится Drupal. До полноценной системы CCK нам далеко, но реализовать динамические поля для товаров своего интернет-магазина на Yii всё-таки стоит.

При переделывании своего проекта на модульную структуру сразу же захотелось сделать модули минимально связанными. При этом многим приходится сталкиваться с организацией выноса настроек модулей из главного конфигурационного файла в сами модули. Рассмотрим несколько приёмов и подводных камней.

На многих новых сайтах всё чаще встречается вывод списка новостей или других сущностей в виде бесконечно подгружающейся ленты. На некоторых сайтах подгрузка выполняется автоматически (на twitter.com или vk.com), на других – вручную, то есть в конце списка вместо стандартного переключателя страниц имеется кнопка «Показать ещё». Освежим в памяти работу с ClistView и попробуем реализовать подобный функционал на своём сайте.

Одна из вечных тем, то и дело всплывающих в сети и касаемых Yii Framework – это спор относительно использования в своих проектах прямых SQL запросов посредством DAO с одной стороны против использования ActiveRecord с другой. Ведь при разрастании объёмов данных и связей между ними в высоконагруженных проектах многие разработчики переходят от удобной объектной модели ActiveRecord к низкоуровневой работе с прямыми SQL запросами и с простыми асcоциативными массивами. Посмотрим, как в некоторых случаях можно разогнать выборки ActiveRecord почти до скорости DAO.

Комментарии

 

Donna Insolita

:-) Бритва Оккама.
В случае админки отдельным модулем или приложением вполне достаточно

public function actionSomething()
{
    throw new CHttpException(404, 'Not found'); // or $this->redirect(array('otheraction'));
}
Ответить

 

Елисеев Дмитрий

Достаточно, но по пять таких заглушек подряд (как в третьем листинге) в некоторых местах будут смотреться весьма странно.

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>