Symfony - How to access entity's repository

Saman picture Saman · Aug 12, 2015 · Viewed 23.1k times · Source

There are several ways that we can access the entity's repository in Symfony2 controllers or services which each has its advantage and disadvantage. First I list them here and then asking if there is any better solution or these are the only options that we have and we should choose one or some based on our preferences. I also want to know if method 5 (which I've started to use it recently) can be good and doesn't break any rule or having any side effects.

Basic Method: Use entity manager in the controller or Inject it to a service and then accessing any repository that I want. This is the basic way of accessing a Repository in controller or service.

class DummyController
{
    public function dummyAction($id)
    {
        $em = $this->getDoctrine()->getManager();
        $em->getRepository('ProductBundle:Product')->loadProduct($id);
    }
}

But there are some problems related to this method. The first problem is that I cannot do Ctrl + click on for example loadProduct function and go directly to its implementation (Unless there is a way that I don't know). The other problem is that I will end up repeating this part of code over and over.

Method 2: The other method is just to define a getter in my service or controller to access my repository.

class DummyService
{
    protected $em;

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

    public function dummyFunction($id)
    {
        $this->getProductRepository()->loadProduct($id);
    }

    /**
     * @return \ProductBundle\Entity\Repository\ProductRepository
     */
    public function getProductRepository()
    {
        return $this->em->getRepository('ProductBundle:Product');
    }
}

This method solves the first problem and somehow the second but still, I have to repeat all getters that I need in my service or controller, also I will have several getters in my services and controllers just for accessing to repositories

Method 3: Another way is to inject a repository to my service, it is nice especially if we have a good control on our code and we are not involved with other developers who inject the entire Container into your service.

class DummyService
{
    protected $productRepository;

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

    public function dummyFunction($id)
    {
        $this->productRepository->loadProduct($id);
    }
}

This method solves the first and second problem, but if my service is big and it needs to deal with a lot of repositories then it is not a nice idea to inject for example 10 repository to my service.

Method 4: Another way is to have a service to wrap all of my repositories and inject this service to other services.

class DummyService
{
    protected $repositoryService;

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

    public function dummyFunction($id)
    {
        $this->repositoryService->getProductRepository()->loadProduct($id);
    }
}

RepositoryService:

class RepositoryService
{
    protected $em;

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

    /**
     * @return \ProductBundle\Entity\Repository\ProductRepository
     */
    public function getProductRepository()
    {
        return $this->em->getRepository('ProductBundle:Product');
    }

    /**
     * @return \CmsBundle\Entity\Repository\PageRepository
     */
    public function getPageRepository()
    {
        return $this->em->getRepository('CmsBundle:Page');
    }
}

This method also solves the first and second problem. But RepositoryService can become so big when we have for example 200 entities.

Method 5: Finally I can define a static method in each entity which returns its repository.

class DummyService
{
    protected $em;

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

    public function dummyFunction($id)
    {
        Product::getRepository($this->em)->loadProduct($id);
    }
}

My Entity:

/**
 * Product
 *
 * @ORM\Table(name="saman_product")
 * @ORM\Entity(repositoryClass="ProductBundle\Entity\ProductRepository")
 */
class Product
{
    /**
     *
     * @param \Doctrine\ORM\EntityManagerInterface $em
     * @return \ProductBundle\Entity\ProductRepository
     */
    public static function getRepository(EntityManagerInterface $em)
    {
        return $em->getRepository(__CLASS__);
    }   
}

This method solves the first and second problem also I do not need to define a service to access repositories. I've used it recently and so far its the best method for me. I don’t think this method will break the rule of entities since it's defined in the class scope and also is so thine. But still I am not sure about it and whether or not it has any side effects.

Answer

qooplmao picture qooplmao · Aug 12, 2015

In the Doctrine world the entity is just supposed to be an anaemic model of getters and setters (and add or removes) so injecting the repository would be wrong thing to do.

It all depends on how coupled you want to be to Doctrine. If you are fine with passing the @doctrine service around then you could just using something like:

$this->repository = $doctrine->getRepository('CmsBundle:Page');

.. but then that, as mentioned, would require you to pass the @doctrine service into every object. This would mean that if you ever decided to not use Doctrine for whatever reason, you would need to refactor all of your code to fit your new methodology (whatever that may be), but this may be a non-issue for you. Also, the repository would be type-hinted so there is no assurance (beyond checking if it is the correct class in code) to guarantee that it is the correct service.

In my opinion the cleanest way to do it is to create a service like:

XML

<service id="cms.page_repository"
    class="Acme\CmsBundle\Repository\PageRepository">
    <factory service="doctrine" method="getRepository" />
    <argument>AcmeDemoBundle:ExampleRepository</argument>
</service>

YAML

cms.page_repository:
    class: Acme\CmsBundle\Repository\PageRepository
    factory: [ @doctrine, 'getRepository' ]

.. and then you can pass your repository service around where ever you want without the need of using the doctrine service in your actual code. With this approach, if you ever decide to move away from Doctrine, you only need to change the service definitions rather than needing to refactor everything. Also due to the fact that you are creating the service of your specific repository you can use type hinting in your __construct to guarantee the correct service is being injected like:

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