1

I'm currently migrating a ZF2 application to ZF3. Mostly everything is going smoothly but I'm stuck on one thing.

In my Module.php, I have an ACL management using zend-permissions-acl.

class Module { protected $defaultLang = 'fr'; public function onBootstrap(MvcEvent $e) { $eventManager = $e->getApplication()->getEventManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); if (!$e->getRequest() instanceof ConsoleRequest){ $eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'onRenderError')); $eventManager->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender')); $eventManager->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish')); $this->initAcl($e); $eventManager->attach('route', array($this, 'checkAcl')); } } public function checkAcl(MvcEvent $e) { $app = $e->getApplication(); $sm = $app->getServiceManager(); $route = $e -> getRouteMatch() -> getMatchedRouteName(); $authService = $sm->get('AuthenticationService'); $jwtService = $sm->get('JwtService'); $translator = $sm->get('translator'); $identity = null; try { $identity = $jwtService->getIdentity($e->getRequest()); } catch(\Firebase\JWT\ExpiredException $exception) { $response = $e->getResponse(); $response->setStatusCode(401); return $response; } if(is_null($identity) && $authService->hasIdentity()) { // no header being passed on... we try to use standard validation $authService->setJwtMode(false); $identity = $authService->getIdentity(); } $userRole = 'default'; $translator->setLocale($this->defaultLang); if(!is_null($identity)) { $userRole = $identity->getType(); //check if client or prospect if($userRole >= User::TYPE_CLIENT) { $userManagementRight = UserRight::CREATE_USERS; if($identity->hasRight($userManagementRight)) $userRole = 'userManagement'; } $translator->setLocale($identity->getLang()); } if (!$e->getViewModel()->acl->isAllowed($userRole, null, $route)) { $response = $e -> getResponse(); $response->setStatusCode(403); return $response; } public function initAcl(MvcEvent $e) { //here is list of routes allowed } } 

My issue here is that I'm still using the getServiceManager and therefore getting the deprecated warning : Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;

Basically, I just need to inject dependencies into Module.php. I guess otherwise I would have to move the checkAcl to the Controller directly and inject the ACL in them ? Not sure what is the proper way of doing this.

Any feedback on this would be greatly appreciated.

Regards,

Robert

1 Answer 1

4

To solve the issue you should use a Listener class and Factory. It would also help you with more separation of concerns :)

You seem quite capable of figuring stuff out, judging by your code. As such, I'm just going to give you an example of my own, so you should fill yours in with your own code (I'm also a bit lazy and do not wish to rewrite everything when I can copy/paste my code in ;) )


In your module.config.php:

'listeners' => [ // Listing class here will automatically have them "activated" as listeners ActiveSessionListener::class, ], 'service_manager' => [ 'factories' => [ // The class (might need a) Factory ActiveSessionListener::class => ActiveSessionListenerFactory::class, ], ], 

The Factory

<?php namespace User\Factory\Listener; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityManager; use Interop\Container\ContainerInterface; use User\Listener\ActiveSessionListener; use Zend\Authentication\AuthenticationService; use Zend\ServiceManager\Factory\FactoryInterface; class ActiveSessionListenerFactory implements FactoryInterface { public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { /** @var ObjectManager $entityManager */ $entityManager = $container->get(EntityManager::class); /** @var AuthenticationService $authenticationService */ $authenticationService = $container->get(AuthenticationService::class); return new ActiveSessionListener($authenticationService, $entityManager); } } 

The Listener

<?php namespace User\Listener; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityManager; use User\Entity\User; use Zend\Authentication\AuthenticationService; use Zend\EventManager\Event; use Zend\EventManager\EventManagerInterface; use Zend\EventManager\ListenerAggregateInterface; use Zend\Mvc\MvcEvent; /** * Class ActiveSessionListener * * @package User\Listener * * Purpose of this class is to make sure that the identity of an active session becomes managed by the EntityManager. * A User Entity must be in a managed state in the event of any changes to the Entity itself or in relations to/from it. */ class ActiveSessionListener implements ListenerAggregateInterface { /** * @var AuthenticationService */ protected $authenticationService; /** * @var ObjectManager|EntityManager */ protected $objectManager; /** * @var array */ protected $listeners = []; /** * CreatedByUserListener constructor. * * @param AuthenticationService $authenticationService * @param ObjectManager $objectManager */ public function __construct(AuthenticationService $authenticationService, ObjectManager $objectManager) { $this->setAuthenticationService($authenticationService); $this->setObjectManager($objectManager); } /** * @param EventManagerInterface $events */ public function detach(EventManagerInterface $events) { foreach ($this->listeners as $index => $listener) { if ($events->detach($listener)) { unset($this->listeners[$index]); } } } /** * @param EventManagerInterface $events */ public function attach(EventManagerInterface $events, $priority = 1) { $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'haveDoctrineManagerUser'], 1000); } /** * @param Event $event * * @throws \Doctrine\Common\Persistence\Mapping\MappingException * @throws \Doctrine\ORM\ORMException */ public function haveDoctrineManagerUser(Event $event) { if ($this->getAuthenticationService()->hasIdentity()) { // Get current unmanaged (by Doctrine) session User $identity = $this->getAuthenticationService()->getIdentity(); // Merge back into a managed state $this->getObjectManager()->merge($identity); $this->getObjectManager()->clear(); // Get the now managed Entity & replace the unmanaged session User by the managed User $this->getAuthenticationService()->getStorage()->write( $this->getObjectManager()->find(User::class, $identity->getId()) ); } } /** * @return AuthenticationService */ public function getAuthenticationService() : AuthenticationService { return $this->authenticationService; } /** * @param AuthenticationService $authenticationService * * @return ActiveSessionListener */ public function setAuthenticationService(AuthenticationService $authenticationService) : ActiveSessionListener { $this->authenticationService = $authenticationService; return $this; } /** * @return ObjectManager|EntityManager */ public function getObjectManager() { return $this->objectManager; } /** * @param ObjectManager|EntityManager $objectManager * * @return ActiveSessionListener */ public function setObjectManager($objectManager) { $this->objectManager = $objectManager; return $this; } } 

The important bits:

  • The Listener class must implement ListenerAggregateInterface
  • Must be activated in the listeners key of the module configuration

That's it really. You then have the basic building blocks for a Listener.

Apart from the attach function you could take the rest and make that into an abstract class if you'd like. Would save a few lines (read: duplicate code) with multiple Listeners.


NOTE: Above example uses the normal EventManager. With a simple change to the above code you could create "generic" listeners, by attaching them to the SharedEventManager, like so:

/** * @param EventManagerInterface $events */ public function attach(EventManagerInterface $events, $priority = 1) { $sharedManager = $events->getSharedManager(); $sharedManager->attach(SomeClass::class, EventConstantClass::SOME_STRING_CONSTANT, [$this, 'callbackFunction']); } public function callbackFunction (MvcEvent $event) {...} 
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks a lot for this, that's an amazing response. I will test it out tomorrow but I believe it's exactly what I need ! :-)
Thank you again, it worked very well. Only issue I had was the priority set to 1000 that made my event occur with no RouteMatched in it. Set it back the default 1 and everything else went smoothly.
Yea, that's just the priority you may give yourself. For me it's an ActiveSessionListener. Early on I want to know who it is that is using my app, so it's triggered early with this priority. At 999 priority I got an Authorization listener to make sure the user determined at 1000 priority gets checked to be allowed on the route ;-)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.