On Taming Repository Classes in Doctrine… Among other things.
A while ago I stumbled upon a - rather old but nonetheless interesting - post written by @beberlei. In his post, he highlighted the issues of having too much responsibilities in a repository and suggested a solution based on the Specification pattern.
The idea behind this pattern is to isolate simple business rules into small specification objects and compose them later in order to express more complex rules. For those who need a reminder, it looks like this:
The combinatorial explosion of methods in the repository is avoided and repositories become unit-testable again.
Have we won? Not quite.
Imagine for instance that we happen to have a use case where we have multiple repositories for the same entities (one in memory and another using Doctrine ORM) and we want our specifications to work with all of them.
At this point you notice that the previous specifications are tied to Doctrine ORM QueryBuilder
objects and that they can not work with anything else. Your carefully crafted specifications can’t be used if you want - and you do - filter a simple array of objects stored in memory.
Luckily, since Doctrine 2.4 we can use Doctrine Criteria
objects to build expressions that can be used to filter both QueryBuilders
and Collections. Moreover, as of version 2.5 (still in alpha at this time) ManyToMany
relations are supported.
Despite being a little too verbose to me, this solution works. We can refactor our previous specifications to return a Criteria
instead of modifying a QueryBuilder
and later apply these criterion to a QueryBuilder
or a Collection
.
The general concept is the same as before but now we use Doctrine Criteria
objects as an intermediate representation for our specifications. As both the EntityRepository
and the ArrayCollection
implement the Selectable
interface, we can use the matching()
method to apply our specification.
Theoretically, our problem should be solved by this solution.
Have we won? Not quite.
Besides the verbosity of these expressions, we would quickly come across another issue: it’s not possible to use custom operators or custom functions.
At this point I started to feel a bit desperate so I wrote my own solution, still based on the Specification pattern but not relying on Doctrine collections at all. I wanted a simple syntax to express my business rules and of course, I wanted to be able to apply them on any kind of target. With that in mind, I created RulerZ (still trying to find a good name for this project…). Based on hoa/ruler, RulerZ is a library which tries to solve this exact problem.
As shown by the previous examples, expressing rules is very easy as instead of creating a lot of objects we just use a simple DSL that is very close to SQL. Applying these rule is as easy as before and so is writing Specification classes. Oh, and I almost forgot: this example also uses a custom operator (“length”).
For those who aren’t convinced, here is how the specification pattern can be implemented with RulerZ and for all the targets you can imagine:
To summarize, Specifications written with RulerZ are more meaningful and yet they can be used on several types of targets. While this project is still a work-in-progress, it’s already tested, documented and integrated in Symfony. Do not hesitate to browse RulerZ’ documentation to discover what other features it has to offer.
Have we won? Hell yeah. Or at least, I hope so.