How to use a plugin method in the onDispatch event?

83 views Asked by At

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);
1

There are 1 answers

7
Ermenegildo On BEST ANSWER

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. ;)