重构对每个 Zf2 控制器操作的一些调用
Refactor some calls on each Zf2 controller action
我需要执行自定义 isGranted
方法(不使用来自社区的 Rbac 或 acl 模块)。所以我有一个提供功能的服务。但是这段代码:
if (!$this->userService->isGrantedCustom($this->session->offsetGet('cod_lvl'), 'ZF_INV_HOM')) {
throw new \Exception("you_are_not_allowed", 1);
}
...在每个控制器和我的每个动作中都是重复的。参数的变化当然取决于权限('ZF_INV_HOM'
、'ZF_TODO_DELETE'
...)。
我认为在调用控制器之前执行此代码是个不错的主意,但我无法确定什么是最佳解决方案(最佳架构),以及如何将这些参数传递给它(我想过控制器上的注释,但如何处理这个?)。
关键是,如果我必须修改这段代码,我无法想象要这样做数百次,对于每个控制器,我拥有的每个动作我都需要将这段代码放在一个地方。
通过将事件侦听器附加到 SharedEventManager,您可以定位所有控制器并在一个地方进行授权检查。
在这种情况下,目标是 Zend\Mvc\Controller\AbstractActionController
,这意味着任何扩展它的控制器都将执行侦听器。此侦听器的高优先级意味着它在目标控制器操作之前执行,让您有机会处理任何未经授权的请求。
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
\Zend\Mvc\Controller\AbstractActionController::class, // Identity of the target controller
MvcEvent::EVENT_DISPATCH,
[$this, 'isAllowed'],
1000 // high priority
);
}
在每个控制器中,您都需要通过某种方式来确定正在访问哪个 'resource'。
作为一个例子,它可以实现这个接口
interface ResourceInterface
{
// Return a unique key representing the resource
public function getResourceId();
}
监听器可能看起来像这样。
public function isAllowed(MvcEvent $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// We need the 'current' user identity
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authService->getIdentity();
// The service that performs the authorization
$userService = $serviceManager->get('MyModule\Service\UserService');
// The target controller is itself a resource (the thing we want to access)
// in this example it returns an resource id so we know what we want to access
// but you could also get this 'id' from the request or config etc
$controller = $event->getTarget();
if ($controller instanceof ResourceInterface) {
$resourceName = $controller->getResourceId();
// Test the authorization, is UserX allowed resource ID Y
if (empty($resourceName) || $userService->isGrantedCustom($identity, $resourceName)) {
// early exit for success
return;
} else {
// Denied; perhaps trigger a new custom event or return a response
}
}
}
如果你不想用所有这些代码污染你的模块,你也可以创建一个监听器 class 并只在你的 bootstrap 方法中附加监听器:
<?php
namespace Application\Listener;
use Application\Service\UserService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\MvcEvent;
use Zend\EventManager\SharedEventManagerInterface;
use Zend\EventManager\SharedListenerAggregateInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class IsAllowedListener implements SharedListenerAggregateInterface
{
/**
* @var AuthenticationServiceInterface
*/
protected $authService;
/**
* @var UserService
*/
protected $userService;
/**
* @var \Zend\Stdlib\CallbackHandler[]
*/
protected $sharedListeners = array();
/**
* @param SharedEventManagerInterface $events
*/
public function attachShared(SharedEventManagerInterface $events)
{
$this->sharedListeners[] = $events->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, array($this, 'isAllowed'), 1000);
}
public function __construct(AuthenticationServiceInterface $authService, UserService $userService ){
$this->authService = $authService;
$this->userService = $userService;
}
/**
* @param MvcEvent $event
*/
protected function isAllowed(MvcEvent $event)
{
$authService = $this->getAuthService();
$identity = $authService->getIdentity();
$userService = $this->getUserService();
if($userService->isGrantedCustom()){
// User is granted we can return
return;
}
// Return not allowed response
}
/**
* @return AuthenticationServiceInterface
*/
public function getAuthService()
{
return $this->authService;
}
/**
* @param AuthenticationServiceInterface $authService
*/
public function setAuthService(AuthenticationServiceInterface $authService)
{
$this->authService = $authService;
}
/**
* @return UserService
*/
public function getUserService()
{
return $this->userService;
}
/**
* @param UserService $userService
*/
public function setUserService(AuthenticationServiceInterface $userService)
{
$this->userService = $userService;
}
}
您需要设置一个工厂来注入您的依赖项:
<?php
namespace Application\Listener;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Factory for creating the IsAllowedListener
*/
class IsAllowedListenerFactory implements FactoryInterface
{
/**
* Create the IsAllowedListener
*
* @param ServiceLocatorInterface $serviceLocator
* @return RenderLinksListener
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$userService = $serviceLocator->get('Application\Service\UserService');
return new IsAllowedListener($authService, $userService );
}
}
并在配置中注册所有这些:
'service_manager' => array(
'factories' => array(
'Application\Listener\IsAllowedListener' => 'Application\Listener\IsAllowedListenerFactory'
)
)
然后在bootstrap:
public function onBootstrap(EventInterface $event)
{
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$isAllowedListener = $serviceManager->get('Application\Listener\IsAllowedListener')
$sharedEventManager->attachAggregate($isAllowedListener);
}
除了使用 AbstractActionController::class
,您还可以制作一个特定的 class,这样您将只会收听 class 的实例。
例如 AbstractIsAllowedActionController::class
或类似的东西。
我需要执行自定义 isGranted
方法(不使用来自社区的 Rbac 或 acl 模块)。所以我有一个提供功能的服务。但是这段代码:
if (!$this->userService->isGrantedCustom($this->session->offsetGet('cod_lvl'), 'ZF_INV_HOM')) {
throw new \Exception("you_are_not_allowed", 1);
}
...在每个控制器和我的每个动作中都是重复的。参数的变化当然取决于权限('ZF_INV_HOM'
、'ZF_TODO_DELETE'
...)。
我认为在调用控制器之前执行此代码是个不错的主意,但我无法确定什么是最佳解决方案(最佳架构),以及如何将这些参数传递给它(我想过控制器上的注释,但如何处理这个?)。
关键是,如果我必须修改这段代码,我无法想象要这样做数百次,对于每个控制器,我拥有的每个动作我都需要将这段代码放在一个地方。
通过将事件侦听器附加到 SharedEventManager,您可以定位所有控制器并在一个地方进行授权检查。
在这种情况下,目标是 Zend\Mvc\Controller\AbstractActionController
,这意味着任何扩展它的控制器都将执行侦听器。此侦听器的高优先级意味着它在目标控制器操作之前执行,让您有机会处理任何未经授权的请求。
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
\Zend\Mvc\Controller\AbstractActionController::class, // Identity of the target controller
MvcEvent::EVENT_DISPATCH,
[$this, 'isAllowed'],
1000 // high priority
);
}
在每个控制器中,您都需要通过某种方式来确定正在访问哪个 'resource'。
作为一个例子,它可以实现这个接口
interface ResourceInterface
{
// Return a unique key representing the resource
public function getResourceId();
}
监听器可能看起来像这样。
public function isAllowed(MvcEvent $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// We need the 'current' user identity
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authService->getIdentity();
// The service that performs the authorization
$userService = $serviceManager->get('MyModule\Service\UserService');
// The target controller is itself a resource (the thing we want to access)
// in this example it returns an resource id so we know what we want to access
// but you could also get this 'id' from the request or config etc
$controller = $event->getTarget();
if ($controller instanceof ResourceInterface) {
$resourceName = $controller->getResourceId();
// Test the authorization, is UserX allowed resource ID Y
if (empty($resourceName) || $userService->isGrantedCustom($identity, $resourceName)) {
// early exit for success
return;
} else {
// Denied; perhaps trigger a new custom event or return a response
}
}
}
如果你不想用所有这些代码污染你的模块,你也可以创建一个监听器 class 并只在你的 bootstrap 方法中附加监听器:
<?php
namespace Application\Listener;
use Application\Service\UserService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\MvcEvent;
use Zend\EventManager\SharedEventManagerInterface;
use Zend\EventManager\SharedListenerAggregateInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class IsAllowedListener implements SharedListenerAggregateInterface
{
/**
* @var AuthenticationServiceInterface
*/
protected $authService;
/**
* @var UserService
*/
protected $userService;
/**
* @var \Zend\Stdlib\CallbackHandler[]
*/
protected $sharedListeners = array();
/**
* @param SharedEventManagerInterface $events
*/
public function attachShared(SharedEventManagerInterface $events)
{
$this->sharedListeners[] = $events->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, array($this, 'isAllowed'), 1000);
}
public function __construct(AuthenticationServiceInterface $authService, UserService $userService ){
$this->authService = $authService;
$this->userService = $userService;
}
/**
* @param MvcEvent $event
*/
protected function isAllowed(MvcEvent $event)
{
$authService = $this->getAuthService();
$identity = $authService->getIdentity();
$userService = $this->getUserService();
if($userService->isGrantedCustom()){
// User is granted we can return
return;
}
// Return not allowed response
}
/**
* @return AuthenticationServiceInterface
*/
public function getAuthService()
{
return $this->authService;
}
/**
* @param AuthenticationServiceInterface $authService
*/
public function setAuthService(AuthenticationServiceInterface $authService)
{
$this->authService = $authService;
}
/**
* @return UserService
*/
public function getUserService()
{
return $this->userService;
}
/**
* @param UserService $userService
*/
public function setUserService(AuthenticationServiceInterface $userService)
{
$this->userService = $userService;
}
}
您需要设置一个工厂来注入您的依赖项:
<?php
namespace Application\Listener;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Factory for creating the IsAllowedListener
*/
class IsAllowedListenerFactory implements FactoryInterface
{
/**
* Create the IsAllowedListener
*
* @param ServiceLocatorInterface $serviceLocator
* @return RenderLinksListener
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$userService = $serviceLocator->get('Application\Service\UserService');
return new IsAllowedListener($authService, $userService );
}
}
并在配置中注册所有这些:
'service_manager' => array(
'factories' => array(
'Application\Listener\IsAllowedListener' => 'Application\Listener\IsAllowedListenerFactory'
)
)
然后在bootstrap:
public function onBootstrap(EventInterface $event)
{
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$isAllowedListener = $serviceManager->get('Application\Listener\IsAllowedListener')
$sharedEventManager->attachAggregate($isAllowedListener);
}
除了使用 AbstractActionController::class
,您还可以制作一个特定的 class,这样您将只会收听 class 的实例。
例如 AbstractIsAllowedActionController::class
或类似的东西。