13

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.

Mohammed Zayan
  • 859
  • 11
  • 20
Saman
  • 5,044
  • 3
  • 28
  • 27

3 Answers3

9

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;
}
carbide20
  • 1,717
  • 6
  • 29
  • 52
qooplmao
  • 17,622
  • 2
  • 44
  • 69
  • If your project is too messy to be able to create and inject a service then you have far bigger issues than best practices. – qooplmao Aug 12 '15 at 11:57
  • 1
    I don't want to inject repository to my entity, the function in method 5 is a static function and it is not in object and entity scope. I am agree with you, the best way is to define a repository service, but if you working in a big and messy project it is hard to do this, but definitely if I want to work on a new one I will follow this pattern. – Saman Aug 12 '15 at 12:05
  • For example one of my services needs to access 5 repository + another 5 services, then I have 10 service injection in my service. I don't think this is a good idea. – Saman Aug 12 '15 at 12:08
  • That seems like a sign that your outer service is doing too much. That being said... even if you are calling doctrine and getting the repository that way you are still passing around 10 services, you are just less sure of what they are. – qooplmao Aug 12 '15 at 12:14
  • Without service-repositories my service have only 5 injection and it is fairly small but if I want to inject extra 5 services just for repositories then I have 10 injections which is not nice. – Saman Aug 12 '15 at 12:29
  • 1
    If "niceness" of your service definitions is what you're going for, then you shouldn't take this approach. But the problem with big service definitions isn't that they're just big, it means that your service probably does too much and knows too much, and moving the resolution of those dependencies inside your class won't solve the problem, it'll just hide it else where. – user2268997 Aug 12 '15 at 13:04
  • Thanks user2268997, I have changed the first example from a service to a controller, because it misleading the question. The question is mainly about how I can access a repository. I am agree with you about the service design. – Saman Aug 12 '15 at 22:55
2

For me, none of your suggestions are correct.
Because I do not understand why you need to create a service of your entity.
If you need access to this entity the only thing you need is to have access to doctrine.
And doctrine has a service (@doctrine).
It's up to you to prepare in the construct to have access only to that entity.

Statics are to forgotten:

And what you submit in method 5 is not correct, your Product entity already access to entityManager via ProductRepository with the getEntityManager() method.

Roukmoute
  • 681
  • 1
  • 11
  • 26
  • 2
    I use services for my repositories with interfaces. It cuts down on the amount that my code is directly coupled with Doctrine when it really isn't necessary. All a service/object requires is that it gets a repository that fits the interface rather than getting Doctrine, to the get the repository (which is also not type checked, so may not have any custom methods). – qooplmao Aug 12 '15 at 09:01
  • 1
    You definitely do NOT "need" doctrine. It's like getting a cow, when all you need is some milk. – user2268997 Aug 12 '15 at 09:52
  • Method 1 is used in Symfony book and cookbook, what do you mean non of them are correct. I don't create a service for my Entity, the ProductService was just an example which I have changed it to a DummyService. The problems about static methods that you have mentioned is right but it is different in this case (method 5). "your Product entity already access to entityManager via ProductRepository with the getEntityManager() method" I don't know what you exactly mean by this sentence. – Saman Aug 12 '15 at 13:05
1

I'll suggest you to use method 4, your service will follow Single Resposability Principe as it only do one thing: giving you an access to all your repositories.

This service will be mostly as a dependency in other services. For controllers, I suggest you to create a custom controller base class with the same helper functions.

About code duplications, traits may be the solution. Even with the numbers of methods if you use a trait by "category"

Yassine Guedidi
  • 1,695
  • 11
  • 12
  • 1
    Thanks Yassine, in my controller also I can use the same service and I don’t need to have a base controller. – Saman Aug 12 '15 at 22:56
  • Just in case you prefer `$this->getMyRepository()` over `$this->get('app.repositories')->getMyRepository()` :) – Yassine Guedidi Aug 12 '15 at 23:00
  • class baseController { public function getRepositories() { return $this->get('app.repositories'); } } class myController extends baseController { public function dummyAction() { $this->getRepositories()->getProductRepo()->loadProduct()}} – Saman Aug 12 '15 at 23:09
  • I mean in your controller code. Your code is wrong, `$this->get('app.repositories')` will return the service, not the repository. – Yassine Guedidi Aug 12 '15 at 23:13
  • I know :) If u look at my above code, I have a getter in my baseController (getRepositories) which return my repository service which aggregates access to other repositories. – Saman Aug 12 '15 at 23:17
  • "I don’t need to have a base controller" Sorry, I mean I dont need to repeat the same helper functions in baseController, – Saman Aug 12 '15 at 23:21
  • Ah OK, it wasn't the code I saw... I just saw a `getMyRepository()` with the code to get the service. – Yassine Guedidi Aug 12 '15 at 23:27
  • So, I'll rewrite my first comment: Repeat helper functions just in case you prefer `$this->getMyRepository()` over `$this->getRepositories()->getMyRepository()` in your controller code. :) – Yassine Guedidi Aug 12 '15 at 23:29
  • BTW, `getRepositories()` isn't a good name, it's mean more like "give me an array of all my repositories". `getRepositoryRegistry()` would be better, as the object you get is the service, a repository registry. – Yassine Guedidi Aug 12 '15 at 23:30