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?
A repository is a way to retrieve entities, so put on repositories any method you need to get them, such as getUserByEmail or whatever.
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.
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.
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.
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.
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.
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.
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();
// ...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With