Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

I have a question about best practices in Symfony 2. Sorry if it's a bit vague and subjective. I guess I can sum up my question as:

"Are repositories always the right place for queries?".

Right now I'm putting most of my doctrine queries in entity repositories. Most of my controller actions do typical things like query for an entity or collection of entities, throw an exception or redirect depending on the outcome of that, otherwise update one or more entities. Most actions are more complex than can be done efficiently with the standard ->find, ->findBy etc queries. Most require joins. When a query involves multiple entities, sometimes I'm not sure which repository it should even go in. I guess there is the root entity of the query but ... sometimes data from the joined entities are more important and relevant so it feels wrong to put it in the root entity's repository.

That's working okay but I tend to end up with lots of almost the same but slightly different queries in my repositories. Coming up with names and keeping track of exactly what each one does can get confusing and tedious. Most of these queries are used by only one or two (often seldom used) controller actions in the same controller. I feel like I'm cluttering my repositories with too much specialized, seldom used stuff.

It seems like all but the most simple actions should be encapsulated in an object or service. So, I've started doing a lot of my queries directly in the service rather than a repository. It's easy to look at the action all in one place. Is this an okay practice?

like image 580
tetranz Avatar asked Jun 13 '13 14:06

tetranz


People also ask

What is repository in Symfony?

A repository is a way to retrieve entities, so put on repositories any method you need to get them, such as getUserByEmail or whatever.

What is doctrine repository?

A repository in a term used by many ORMs (Object Relational Mappers), doctrine is just one of these. It means the place where our data can be accessed from, a repository of data. This is to distinguish it from a database as a repository does not care how its data is stored.

Does Symfony use doctrine?

Symfony provides all the tools you need to use databases in your applications thanks to Doctrine, the best set of PHP libraries to work with databases. These tools support relational databases like MySQL and PostgreSQL and also NoSQL databases like MongoDB.

How should be the process to add a new entity to the app in Symfony?

With the doctrine:database:create command we create a new database from the provided URL. With the make entity command, we create a new entity called City . The command creates two files: src/Entity/City. php and src/Repository/CityRepository.


3 Answers

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.

like image 151
Nicolai Fröhlich Avatar answered Sep 19 '22 15:09

Nicolai Fröhlich


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.
like image 1
Kamafeather Avatar answered Sep 21 '22 15:09

Kamafeather


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.

like image 28
Pier-Luc Gendreau Avatar answered Sep 19 '22 15:09

Pier-Luc Gendreau