11

I would like to know what is the best practice for EventDispatcher injection in EntityRepository class.

Ellis
  • 328
  • 3
  • 12

1 Answers1

21

First, using global is a very bad practice. I strongly advise you not to do this.
Second, Injecting services into a repository doesn't seem like a good idea. It will often break laws like the Single Responsibility Principle.

I'd create a manager that will wrap the methods of your repository, and will trigger the events you need. See how to inject repository to a service for further information.

services.yml

services:
    my_manager:
        class: Acme\FooBundle\MyManager
        arguments:
            - @acme_foo.repository
            - @event_dispatcher

    acme_foo.repository:
        class: Acme\FooBundle\Repository\FooRepository
        factory_service: doctrine.orm.entity_manager
        factory_method: getRepository
        arguments:
            - "AcmeFooBundle:Foo"

Acme\FooBundle\MyManager

use Acme\FooBundle\Repository\FooRepository;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class MyManager
{
    protected $repository;
    protected $dispatcher;

    public function __construct(FooRepository $repository, EventDispatcherInterface $dispatcher)
    {
        $this->repository = $repository;
        $this->dispatcher = $dispatcher;
    }

    public function findFooEntities(array $options = array())
    {
        $event = new PreFindEvent;
        $event->setOptions($options);

        $this->dispatcher->dispatch('find_foo.pre_find', $event);

        $results = $this->repository->findFooEntities($event->getOptions());

        $event = new PostFindEvent;
        $event->setResults($results);

        $this->dispatcher->dispatch('find_foo.post_find', $event);

        return $event->getResults();
    }
}

Then you can use it in your controller, just like a service.

$this->get('my_manager')->findFooEntities($options);

However, if you really need to inject the event dispatcher into your entity, you can do this

services.yml

services:
    acme_foo.repository:
        class: Acme\FooBundle\Repository\FooRepository
        factory_service: doctrine.orm.entity_manager
        factory_method: getRepository
        arguments:
            - "AcmeFooBundle:Foo"
        calls:
            - [ "setEventDispatcher", [ @event_dispatcher ] ]

Then you just have to add the setEventDispatcher method to your repository.

Acme\FooBundle\Repository\FooRepository

class FooRepository extends EntityRepository
{
    protected $dispatcher;

    public function setEventDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function findFooEntities(array $options = array())
    {
        $dispatcher = $this->dispatcher;

        // ...
    }
}

Just make sure you call the service and not the repository when using it in the controller.

DO

$this->get('acme_foo.repository')->findFooEntities();

DON'T

$this->getDoctrine()->getManager()->getRepository('AcmeFooBundle:Foo')->findFooEntities();
Community
  • 1
  • 1
Touki
  • 7,465
  • 3
  • 41
  • 63
  • I cam here having the same problem, and your answer makes sense. However, now the Manager has 2 responsibilities, proxying the repository and raising the events. This is a very "pedantic" question, but where do you stop refactoring to get the SRP? – JorgeeFG Dec 27 '16 at 15:37
  • @JorgeeFG IMO, you can refactor it once again by having an `OptionsResolver::resolve($options)` and `ResultHandler::handle($results)` (which will both call an event) given in your Manager. The manager will then act as a simple Facade which will connect all three components. However, I really feel like adding these two interfaces would start to get overkill since there's hardly any improvement/changes you would expect by changing their behaviour since most of them can be done in event handlers. – Touki Dec 31 '16 at 17:13