Jump to: TL;DR
In my previous post, I tried to answer the following question: how do you keep your Doctrine repositories from growing exponentially? Long story short, I came up with a generic solution based on the Specification pattern that essentially abstracts and simplifies the way we write and compose queries. And the best part is that it works with Doctrine but also with any other data-source.
RulerZ was born.
Of course, there was a real need behind my previous question. For one of my current projects, I wanted to be able to switch from one data-source to another. My application would use Doctrine in development and production environment but for tests I wanted my data to live in memory.
First thing first, I needed to be able to manipulate my data.
To achieve that, I defined a
CompanyRepository interface — it’s a
small project that mainly deals with companies — and wrote two classes
implementing it: one using Doctrine and another one storing my objects in memory.
In the beginning, I only needed to save a company and retrieve it by it’s slug so both implementations were simple and it all worked as expected. The real issue came when I started to implement a search engine. For each new search criteria, I had to update two classes and implement the same criteria twice.
At this point, my repositories looked like this:
The first issue is that my
method violates the open/closed principle.
This is solved by replacing the
$criteria parameter by specifications.
Each specification representing a single search criteria.
The second issue is that with “classic” specifications, the same specification can’t be used to filter data from several data-sources. We saw that with RulerZ, this problem is solved.
Using RulerZ, the two previous repositories can be refactored:
You probably noticed a not so subtle difference compared to the old repositories: I rely on RulerZ so I have to inject it somehow. Lucky me, I’m working on a Symfony application and there is a bundle for that!
As RulerZ is now properly integrated into my repositories and I’m able to query my data, I need to address the specification-creation issue. Remember, I was trying to implement a search engine so how do I map a search as expressed by a user (through a form for instance) to a specification?
The idea here is to map a user input — a search criteria — to a specification. A string (or a scalar) to an object. Using Symfony Form component. Looks a lot like a DataTransformer don’t you think?
With that in mind, I wrote the following form type:
The form type itself is pretty straightforward, the only thing to notice is the
SpecToStringTransformer. It takes two parameters: the
FQCN of the specification to
build and a property in this specification containing the value.
The transformer’s code itself isn’t really important but if you really want to
read it, it’s available as a gist.
What’s important is that combining the Form Component, a simple transformer and RulerZ leads to really simple controllers:
Do you see the magic? A classic form type can automatically build specifications that can be used to retrieve data from a Doctrine repository, an in-memory repository or virtually any other data-source. Also, adding new search criteria boils down to writing its specification and adding a new field in the form type. How cool is that?
Using RulerZ and Symfony you can:
- use specification objects inside Doctrine repositories to query your data ; * make the Form component build specification objects for you.