重构每个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'…)

我认为在控制器被调用之前做这个代码不是一个坏主意,但我不知道什么是最好的解决方案(最好的架构),以及如何将这些参数传递给它(我考虑过控制器上的注释,但如何处理这个?)。

关键是,如果我要修改这段代码我无法想象要修改几百次,对于每个控制器,每个动作我需要在一个地方有这段代码

如果你不想让所有这些代码污染你的模块,你也可以创建一个侦听器类,并只在你的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 );
    }
}

并在config中注册所有这些:

'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,这样您将只侦听该类的实例。

比如AbstractIsAllowedActionController::class之类的

通过将事件监听器附加到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
    );
}

在每个控制器中,需要有一些方法可以确定正在访问哪个"资源"。

作为一个例子,它可以实现这个接口

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
        }
    }
}