Перенаправление в родительский контроллер
В моем проекте ZF2 (2.4.5) у меня есть основной (родительский) контроллер с функцией проверки прав пользователя, чтобы каждый унаследованный контроллер мог легко получить к нему доступ. Однако есть проблема с перенаправлением. Я знаю, что унаследованное действие контроллера должно возвращать ответ, но возможно ли принудительное перенаправление в родительском контроллере?
Родительский контроллер
<?php
namespace Application;
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function checkAccess($moduleName, $accessName){
if($this->getAclService()->isAllowed($moduleName, $accessName)){
self::redirect()->toRoute('access-denied');
}
}
}
Унаследованный контроллер
namespace Application\Controller;
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
$this->checkAccess('Foo', 'Bar');
return new ViewModel([
]);
}
}
TL;DR Если я позвоню $this->checkAccess('Foo', 'Bar');
в InterfaceController
а также $this->getAclService()->isAllowed($moduleName, $accessName)
в CoreController
возвращается false
Я хочу перенаправить пользователя на маршрут 'access-denied'
сразу без завершения остальных InterfaceController::indexAction
Важно: я хочу избежать проверки того, что checkAccess
возвращается, я просто форсирую перенаправление.
Заранее спасибо за ответ.
3 ответа
Хорошо, я сделал это с помощью глобального обработчика исключений
Дочерний контроллер
<?php
namespace Warehouse\Controller;
use Application\CoreController;
use Zend\View\Model\ViewModel;
class IndexController extends CoreController {
public function getWarehouseDocumentAction() {
parent::checkAccess('Warehouse', 'incoming-goods');
return new ViewModel([
'foo' => 'bar',
]);
}
}
Родительский контроллер
namespace Application;
use Application\Exception\InsufficientPermissionException;
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController {
public function checkAccess($moduleName, $accessName){
if(!$this->getServiceLocator()->get(MyAcl::class)->isAllowed($moduleName, $accessName, $this->identity())){
throw new InsufficientPermissionException('Access denied. Insufficient permission.');
}
}
}
module.php
<?php
namespace Application;
use Application\Exception\InsufficientPermissionException;
use Application\Monolog\Handler\DoctrineLogMessageHandler;
use Zend\Mvc\MvcEvent;
class Module {
public function onBootstrap(MvcEvent $e) {
$sharedEvents = $e->getApplication()->getEventManager()->getSharedManager();
$sharedEvents->attach('Zend\Mvc\Application', 'dispatch.error', function (MvcEvent $event) {
if (php_sapi_name() !== 'cli') {
$exception = $event->getParam('exception');
if ($exception instanceof InsufficientPermissionException) {
$target = $event->getTarget();
return $target->redirect()->toRoute('access-denied');
}
}
});
}
}
Разрешения хранятся в базе данных.
Я выхожу на конечность здесь, говоря, что это невозможно. Вызывающий код InterfaceController::indexAction должен по крайней мере вернуть, что запустит процесс перенаправления.
Вы можете немного его очистить, позволив базовому контроллеру установить перенаправление, но унаследованный контроллер должен остановить выполнение скрипта, вызвав return.
Базовый контроллер
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
return false;
} else {
return true;
}
}
}
Унаследованный контроллер
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
if (!$this->checkAccess('Foo', 'Bar')) {
return;
}
return new ViewModel([
]);
}
}
РЕДАКТИРОВАТЬ
Напомним, что в нашей компании мы выполняем проверки ACL в методе init() базового контроллера с not overridded и поэтому можем выполнить перенаправление мгновенно перед выполнением любого кода действия.
РЕДАКТИРОВАТЬ № 2
Я полностью забыл о методе init, который мы используем. Попробуйте, если вы ищете другое решение.
Базовый контроллер
use Zend\Mvc\Controller\AbstractActionController;
class CoreController extends AbstractActionController{
public function init() {
// bootstrap code...
// This should redirect
if (!$this->_isUserAuthorized()) {
return;
}
// If the ACL check was OK then pass control to controllers
return parent::init();
}
private function _isUserAuthorized() {
// checks done here
// return true if OK
// else
$this->_response->setRedirect($this->view->defaultUrl($redirect))->sendResponse();
return false;
}
}
Унаследованный контроллер
use Application\CoreController;
use Zend\View\Model\ViewModel;
class InterfaceController extends CoreController{
public function indexAction(){
// nothing to do here
return new ViewModel([]);
}
}
Вы делаете простое http-перенаправление 302 на "доступ запрещен", так что вы можете просто визуализировать объект ответа и остановить выполнение php:
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
$this->getResponse()->send();
exit;
} else {
return true;
}
}
или вы можете просто выбросить исключение:
public function checkAccess($moduleName, $accessName){
if (!$this->getAclService()->isAllowed($moduleName, $accessName))) {
self::redirect()->toRoute('access-denied');
throw new \Exception('access denied');
} else {
return true;
}
}
исключение предотвратит дальнейшее выполнение кода, а перенаправление предотвратит отображение страницы ошибки исключения.