2
votes

I have an abstract class for my Doctrine 2 Entity. How do I inject the Service Locator to get for example the Translator or Zend\Mvc\Controller\Plugin\Url or how do I directly inject these plugins to the abstract entity class.

The goal is to get the doctrine entity from the entity repository and manipulate the result of the entity within an abstract entity model/service.

Doctrine 2 Entity in short form:

namespace Rental\Entity;

use Doctrine\ORM\Mapping as ORM;
use Rental\Model\Rental as AbstractRental;

/**
 * Rental
 *
 * @ORM\Entity(repositoryClass="Rental\Repository\Rental") *
 * @ORM\Table(name="rental", options={"collate"="utf8_general_ci"})
 */
class Rental extends AbstractRental{

    ...

    public function getType(){
    ...
    }

    ... entity setter and getter

}

Abstract entity model:

namespace Rental\Model;

use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

/**
 * Rental\Model\Rental
 * @ORM\MappedSuperclass
 * @ORM\HasLifecycleCallbacks
 */
abstract class Rental implements ServiceLocatorAwareInterface
{
    protected $serviceLocator;
    protected $translator;

    abstract protected function getType();

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }

    public function getTranslator()
    {
        if (!$this->translator) {
            $this->translator = $this->getServiceLocator()->get('translator');
            // here is the problem, because getServiceLocator is NULL
        }

        return $this->translator;
    }

    public function getTranslatedType(){            
        return $this->translator->translate($this->getType())
    }

This is not working because the abstract class is not instantiated and so the ServiceLocatorInterface is not injected.

Here is my Controller:

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;  
use Doctrine\ORM\EntityManager;


namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;  
use Doctrine\ORM\EntityManager;


class IndexController extends AbstractActionController
{
    /**
     * @var \Doctrine\ORM\EntityManager
     */
    protected $em;


    /**
     * @param \Doctrine\ORM\EntityManager $em
     */
    public function setEntityManager(EntityManager $em)
    {
        $this->em = $em;
    }


    /**
     * @return array|\Doctrine\ORM\EntityManager|object
     */
    public function getEntityManager()
    {
        if (NULL === $this->em) {
            $this->em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
        }

        return $this->em;
    }


    /**
     * @return \Zend\View\Model\ViewModel
     */
    public function indexAction()
    {
        ...

        /** @var $repository \Rental\Repository\Rental */
        $repository = $this->getEntityManager()->getRepository('Rental\Entity\Rental');

        /** @var $rentals array */
        $rental= $repository->findBySlug($slug);
        \ChromePhp::log($rental->getTranslatedType()); 
        // is NULL
1
This link helps me to inject the service manager into the doctrine entity. http://michaelthessel.com/injecting-zf2-service-manager-into-doctrine-entities/ But now I stuck with the controller plugins like Zend\Mvc\Controller\Plugin\Url.Oskar
Please, don't do that. Entities should be very simple and plain objects without any complex logic in it. Translation is task for another application layer (service layer, view, etc.)lku
I know. Because of that I have this abstract class to have these complex logic not in my entity. @Iku: Have you a better approach?Oskar
As I said, I'd move this logic to another layer. If we talking about translation in your example, I'd inject translator in controller and translate it there.lku
That's a good approach, but I use my entity in a lot of controllers/views and I don't want to do the same translations/manipluations in every controller/view. Too much duplicate code. I'm looking for a central place to do that. For now it is this abstract class of the entity.Oskar

1 Answers

-1
votes

This is one way to accomplish your goal of injecting the ZF2 Service Locator into your entities that inherit from the abstract class

Remember to change/fix any namespaces

First, you'll need to register an event listener with Doctrine. You only need to listen for postLoad. To do this, instantiate the listener (which we'll define next) and pass it it's sole dependency, which is the service locator.

Module.php

// in your Module.php
use Rental\Model\Listeners\EntityInjectorListener;

public function onBootstrap(MvcEvent $e)
{
    $serviceLocator = $e->getApplication()->getServiceManager();
    $entityManager = $serviceLocator->get('Doctrine\ORM\EntityManager');
    $entityManager->getEventManager()->addEventListener(array(\Doctrine\ORM\Events::postLoad), new EntityInjectorListener($serviceLocator));
}

Now define a listener for Doctrine to use. It should check the entity it's working on and if it inherits from your abstract class, then it should set the service locator.

//  EntityInjectorListener.php
namespace \Rental\Model\Listeners;

class EntityInjectorListener
{
    protected $serviceLocator;

    public function __construct($serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

     public function postLoad($eventArgs) 
     {
        // check if entity is a child of abstract class
        if ($eventArgs->getEntity() instanceof \Rental\Model\Rental) {
            $eventArgs->getEntity()->setServiceLocator($this->serviceLocator);
        }
     }
}

Now within your entity you should be able to call
$this->getServiceLocator()->get('whatever-you-want-get');

To use view helpers you will to need call them like this:

$this->getServiceLocator()->get('ViewHelperManager')->get('url');