Symfony best practices. Should queries be in repositories or services?

You can do something inbetween.

Define a service:

blog.post_manager:
    class: Acme\BlogBundle\Entity\Manager\PostManager
    arguments:
        em: "@doctrine.orm.entity_manager"
        class: Acme\BlogBundle\Entity\Post

Then create the Manager class:

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;

class PostManager
{
    protected $em;

    protected $repo;

    protected $class;

    public function __construct(EntityManager $em, $class) {
        $this->em = $em;
        $this->class = $class;
        $this->repo = $em->getRepository($class);
    }

    public function get($id)
    {
        return $this->repo->findById($id);
    }
}

This way, you can still leave queries where they belong, in repositories, while allowing code re-use through the manager service which can be used like this in any controller:

$this->container->get('blog.post_manager')->get(1);

Since the service takes care of injecting the class and entity manager to the Manager class, this also keeps the controller thinner and better abstracts it away from the model.


Your queries should be kept in your entity repositories and not in your controllers to be able to re-use them easily.

That's what the repositories are for actually. Providing a re-usable location for the database queries.

There are however some situations where keeping all your queries in the repository can be improved ... especially when it comes to filtering where quickly a lot of queries may be needed.

Benjamin Eberlei ( creator of Doctrine ) considers 5 public methods in a class to be okay and 10 to be fairly large. He has recently published an interesting article called "On Taming Repository Classes in Doctrine" about this on his blog.

I partly do like the filterable repository trait solution by KnpLabs in their DoctrineBehaviors aswell.

Traits make testing harder but you can have a cleaner and easier to maintain repository ... where you should keep your queries.


The repository responsibility is to provide a non-technical interface to your domain objects.
The actual query implementation can stay in the repository, but is better to encapsulate it in a class that has the single responsibility to build the query.
Controllers (or application services) shouldn't contain the implementation detail of the query, they should just call the repository (as if it was a collection) or a query factory (if you want to handle the querying or the result yourself).
Ideally controllers should have little code and just call to "SRPecialized" services.



You can try to implement this simple architectural option. It fits Doctrine or whatever other ORM library. It honours separation of concerns, as intended in Martin Fowler's PoEAA book, providing a layer for abstraction of persistence details and composition.


Why a repository?

Repositories responsibility is to handle a collection, so actual operational code should go there. The queries should be collected/composed/generated somewhere else, like in a factory class (as mentioned previously). This allows to switch the underlying persistence layer implementation without touching the part of domain/application logic that is held inside Repositories.

You can derive from this, depending on your application, but when queries start to be dynamic, with several parameters, or simply too long, I tend to abstract each query in its own class, and just call them from the repository.

Query construction

Each query can have a static constructor that is called by the repository. For more complex cases alternative I define a simple QueryFactory as entrypoint for the available custom queries; this will allow a more flexible configuration of dependencies, thanks to Symfony DI.


An example of implementation

namespace App\Repository;

// use statements

class ArticleRepository extends Doctrine\ORM\EntityRepository
{
    public function getPublished()
    {
        $query = App\Query\Article::getPublished(\DateTimeImmutable $after = null);
        $query_iterator = new Doctrine\ORM\Tools\Pagination\Paginator($query);
        
        // ... run query ...
    }
}

so you can have custom methods for specific variations of the query (e.g. getPublished, getUnpublished, getNewArticles, or whatever else).

namespace App\Query;

// use statements

final class Articles
{
    public static function getPublished(\DateTimeImmutable $after = null)
    {
        return new self(Article::PUBLISHED_STATE, $this->sqlFormat($after));
    }

    public function __constructor(string $status, \DateTimeImmutable $date_filter = null) {
        
        // ... you own complex query logic ...
    }
}

// ...

$published_query = Articles::getPublished();

// ...
  • The maintenance becomes slightly annoying because of proxying through the static constructors and having one more file to go through when tracing back a bug on the stack. Although it scales up better.
  • in general it gives a good & flexible organization in project that spawn many complex or non-standard queries.
  • in my opinion the Repository Pattern is supposed to be agnostic about implementation details at persistence level (what database adapter is used under the hood: SQL? NoSQL?).
  • composition, and abstraction of parts that are common for queries from different repository, are more easily achievable.