Наследование 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 под новую структуру.

Комментарии

 

Donna Insolita

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

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

 

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

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

Ответить

 

Denis Klimenko

относительно недавно пришел к такоку аля generic-контроллеру, указываем классы которые нам помогут и готово

class BucketController extends AdminRepositoryController
{
    protected $repository = BucketRepository::class;
    protected $model      = Bucket::class;
    protected $viewModel  = BucketView::class;

    protected $entityName  = 'bucket';
    protected $routePrefix = 'admin.bucketmanage.buckets';
}



abstract class AdminRepositoryController
{
    /** @var AdminRepositoryContract */
    protected $repository;
    protected $model;
    protected $viewModel;

    protected $entityName;
    protected $routePrefix;

    protected $storeRequest;
    protected $updateRequest;


    public function __construct()
    {
        $this->viewModel = App::make($this->viewModel);
        $this->repository = App::make($this->repository);

        View::share('routePrefix', $this->routePrefix);
        View::share('entityName', $this->entityName);
    }


    public function index(Request $request)
    {
        return View::make($this->getIndexView(), [
            'grid'        => $this->viewModel->getGrid($request),
            'routePrefix' => $this->routePrefix,
            'entityName'  => ucfirst($this->entityName)
        ]);
    }


    public function create()
    {
        return $this->showEditForm(new $this->model);
    }


    public function store()
    {
        $request = App::make($this->storeRequest ?? Request::class);

        $entity = $this->repository->createByAdmin($request);

        return Redirect::route($this->routePrefix . '.edit', [$entity->id]);
    }


    public function edit($id)
    {
        return $this->showEditForm($this->repository->findFirstById($id));
    }


    public function update($id)
    {
        $request = App::make($this->updateRequest ?? Request::class);

        $entity = $this->repository->updateByAdmin($request,
            $this->repository->findFirstById($id)
        );

        return Redirect::route($this->routePrefix . '.edit', [$entity->id]);
    }


    public function destroy($id)
    {
        $this->repository->deleteEntity($id);
    }


    protected function showEditForm($entity)
    {
        $formFields = $this->getFormFields($entity);
        $viewModel = $this->viewModel;

        return View::make($this->getEditView(), [
            'entity'     => $entity,
            'formFields' => !empty($formFields) ? $formFields : $viewModel::getFormFields($entity),
        ]);
    }


    protected function getIndexView()
    {
        return 'admin.components.genericView.index';
    }


    protected function getEditView()
    {
        return 'admin.components.genericView.edit';
    }


    protected function getFormFields($entity): array
    {
        return [];
    }
}

Ответить

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

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


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



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