Injecting ZF2 Service Manager into Doctrine Entities

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=&quot;foo&quot;)
 * @ORM\HasLifecycleCallbacks
 */
class Foo extends ServiceLocatorAwareEntity
{

}

I'm available for contracting work. Check out my LinkedIn profile and my portfolio page for an overview of my skills and experience. If you are interested in working with me please use the contact form to get in touch.

Injecting ZF2 Service Manager into Doctrine Entities

8 thoughts on “Injecting ZF2 Service Manager into Doctrine Entities

    1. Michael Thessel says:

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

  1. Alexander Margraf says:

    Hi,

    i migrate from ZF1 to Zf2. Where do i store physically ServiceManagerListener Class?

    1. Michael Thessel says:

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

  2. Ivo says:

    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

    1. Michael Thessel says:

      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.

  3. 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

Leave a Reply

Your email address will not be published. Required fields are marked *