phpzend-framework3zend-controller-plugin

How to use a plugin method in the onDispatch event?


I have a plugin which looks up the user grants.

I declarated it in my module.config.php as follows:

'controller_plugins' => [
    'factories' => [
        Controller\Plugin\AccessPlugin::class => function($container) {
            return new Controller\Plugin\AccessPlugin(
                $container->get(Model\UserTable::class),
                $container->get(Model\GrantsTable::class),
                $container->get(Model\Authentication::class)
                );
        },
    ],
    'aliases' => [
        'access' => Controller\Plugin\AccessPlugin::class,
    ]
],

In my onDispatch(MvcEvent $event) event I want to fetch the http routing parameters, look up the grant and, if successful, redirect to the correct route.

  public function onDispatch(MvcEvent $event)
    {
            $controller = $event->getTarget();
            $controllerName = $event->getRouteMatch()->getParam('controller', null);
            $actionName = $event->getRouteMatch()->getParam('action', null);
            $actionName = str_replace('-', '', lcfirst(ucwords($actionName, '-')));
            $this->access()->checkAccess($controllerName, $actionName);


....

Of course the plugin can't be find, it isn't loaded yet:

Call to undefined method User\Module::access()

I'd like to call the plugin method anyway. Is there a possibility to use it in this case? Something like this:

        $grantplugin = fetch/call the plugin
        $isgranted = $grantplugin->checkAccess($controllerName, $actionName);

Any help appreciated!

**EDIT: I tried the solution ermengildo gave me. But it doesn't work, the reason is, that I haven't worked with factories yet. With a bit of help I can probably learn how to do it properly. Here my nippets:

I located the two services here:

location of services

I changed the module.php to (snippet!):

public function onDispatch(MvcEvent $event) {

    $controller = $event->getTarget();
    $controllerName = $event->getRouteMatch()->getParam('controller', null);
    $actionName = $event->getRouteMatch()->getParam('action', null);
    $actionName = str_replace('-', '', lcfirst(ucwords($actionName, '-')));
    $container = $event->getApplication()->getServiceManager();
    $accessService = $container->get(Service\AccessControl::class);
    $accessService->access()->checkAccess($controllerName, $actionName);

Last I tried to declarate the service as a factory:

'service_manager' => [
    'factories' => [
        // Avoid anonymous functions
        Service\AccessControl::class => Service\AccessControlFactory::class,
    ],
],

Remark: Here already I have the syntax warning: ...cannot be resolved to a type

If I debug, I get an exception:

Unable to resolve service "User\Service\AccessControl" to a factory; are you certain you provided it during configuration?

In the further explanation this line in my module.php shall be the problem:

 $accessService = $container->get(Service\AccessControl::class);

Solution

  • The problem isn't that "the plugin hasn't been loaded yet", but that you are not inside a controller, and you are trying to call a controller plugin.

    What you must do here is to create a service, retrive an instance from the service manager, and call the method on that instance.

    Example

    module.config.php

    'service_manager' => [
        'factories' => [
            // Avoid anonymous functions
            \User\Service\AccessControl::class => \User\Service\AccessControlFactory::class
        ]
    ]
    

    AccessControlFactory

    namespace User\Service;
    
    use Zend\ServiceManager\Factory\FactoryInterface;
    use User\Service\AccessControl;
    
    class AccessControlFactory implements FactoryInterface {
    
        /**
         * Factory called
         *
         * @param \Interop\Container\ContainerInterface $container
         * @param string $requestedName
         * @param array|null $options
         * @return TranslatorPlugin
         */
        public function __invoke(\Interop\Container\ContainerInterface $container, $requestedName, array $options = null) {
            $userTable = $container->get(\User\Model\UserTable::class);
            $grantsTable = $container->get(\User\Model\GrantsTable::class);
            $authentication = $container->get(\User\Model\Authentication::class);
            return new AccessControl($userTable, $grantsTable, $authentication);
    
        }
    
    }
    

    AccessControl

    namespace User\Service;
    
    use User\Model\UserTable;
    use User\Model\GrantsTable;
    use User\Model\Authentication;
    
    class AccessControl {
    
        private $userTable;
        private $grantsTable;
        private $authentication;
    
        function __construct(UserTable $userTable, GrantsTable $grantsTable, Authentication $authentication) {
            $this->userTable = $userTable;
            $this->grantsTable = $grantsTable;
            $this->authentication = $authentication;
    
        }
    
        public function access(){
            // Do your stuff
        }
    
    }
    

    And finally, you can create it in your Module class and use it:

    User\Module

    public function onDispatch(MvcEvent $event) {
        $controller = $event->getTarget();
        $controllerName = $event->getRouteMatch()->getParam('controller', null);
        $actionName = $event->getRouteMatch()->getParam('action', null);
        $actionName = str_replace('-', '', lcfirst(ucwords($actionName, '-')));
        $container = $event->getApplication()->getServiceManager();
        $accessService = $container->get(\User\Service\AccessControl::class);
        $accessService->access()->checkAccess($controllerName, $actionName);
    
    }
    

    Side note

    From your snippets, it is quite obvious what you are trying to achieve. I'd suggest you to take a look at this question/answer, and at the comment done by rkeet, because that's the proper way to do access controls. ;)