Making ZF2 services available within Doctrine entities is not as trivial as with objects that are created via the ZF2 factory methods. The problem is that Doctrine Entities are not generated via regular ZF2 factory methods but by the Doctrine Entity Manager. Nevertheless it’s possible to inject dependencies into Doctrine entities.
Doctrine’s event manager offers a postLoad event which is called after the entity has been loaded. We can add a new listener to this event during our regular bootstrapping process.
i.e. module/Application/Module.php
public function onBootstrap(MvcEvent $e) { $sm = $e->getApplication()->getServiceManager(); $em = $sm->get('doctrine.entitymanager.orm_default'); $dem = $em->getEventManager(); $dem->addEventListener(array(\Doctrine\ORM\Events::postLoad), new ServiceManagerListener($sm)); }
Next we need to implement the listener class. Please note that with this implementation only entities that implement ServiceLocatorAwareInterface will get the service manager injected. The nice thing about this is that this makes the entities compatible with ZF2 factories. This way entities that are created via ZF2 factories will get the service manager injected as well.
<?php use Zend\ServiceManager\ServiceManager; use Zend\ServiceManager\ServiceLocatorAwareInterface; class ServiceManagerListener { protected $sm; public function __construct(ServiceManager $sm) { $this->sm = $sm; } public function postLoad($eventArgs) { $entity = $eventArgs->getEntity(); $class = new \ReflectionClass($entity); if ($class instanceof ServiceLocatorAwareInterface)) { $entity->setServiceLocator($this->sm); } } }
You could implement the ServiceLocatorAwareInterface for each entity individually but this seems to be tedious. Instead we will create a ServiceLocatorAwareEntity class which implements Zend\ServiceManager\ServiceLocatorAwareInterface and then simply make the entities that need access to the service manager inherit from this class.
<?php use Zend\ServiceManager\ServiceLocatorAwareInterface; use Zend\ServiceManager\ServiceLocatorInterface; class ServiceLocatorAwareEntity implements ServiceLocatorAwareInterface { protected $sm; /** * Set the service locator * * @param ServiceLocatorInterface $sm * * @return void */ public function setServiceLocator(ServiceLocatorInterface $sm) { $this->sm = $sm; } /** * Get the service locator * * @return ServiceLocator ServiceLocator instance */ public function getServiceLocator() { return $this->sm; } }
As mentioned above inherit from ServiceLocatorAwareEntity. Make sure to add @ORM\HasLifecycleCallbacks to make sure doctrine triggers the postLoad event for this entity.
/** * Foo * * @ORM\Entity * @ORM\Table(name="foo") * @ORM\HasLifecycleCallbacks */ class Foo extends ServiceLocatorAwareEntity { }
Hi,
I’m newbie with ZF2. Can you explain how I call this service in controller?
Thanks in advance.
Hi Ivo, this is unrelated to controllers. This implementation is specific to using the ZF2 service manager within Doctrine entities.
If you want to use the service manager in a controller you can simply do this:
$serviceManager = $this->getServiceLocator();
Hi,
i migrate from ZF1 to Zf2. Where do i store physically ServiceManagerListener Class?
Hi Ivo,
You can put it anywhere you want. I personally have a set of my own libraries in the vendor directory. You could add it to the Application module too.
For example:
module/Application/src/Application/Model/Listener/ServiceManagerListener.php
You would need to add a namespace statement at the beginning of the file:
namespace Application\Model\Listener;
and modify the line that adds the event listener to:
$dem->addEventListener(
array(\Doctrine\ORM\Events::postLoad),
new \Application\Model\Listener\ServiceManagerListener($sm)
);
Hi Michael,
Thanks for your answers.
I have implemented your code in my project. How I should run a query in my controller?
Before I’m doing this:
$results = $this->getServiceLocator()->get(‘doctrine.entitymanager.orm_default’)->getRepository(‘Application\Entity\Test’)->findAll()
How I should replace this?
Thanks in advance.
Best Regards
Ivo
That should still work. You maybe want to have a look at the DAO pattern. Try to keep your controllers as skinny as possible and move all your business logic into your models.
Hi Michael,
Thanks for the post,
You can also use a trait as of php 5.4 rather than extending a ServiceLocatorAwareEntity class.
In fact a trait exists already in ZF2 current version which does just that: Zend\ServiceManager\ServiceLocatorAwareTrait
Regards
Mat
Hi, thanks for the post, this is the best decision I’ve ever seen, Thanks man )