Alternative Method to Tame Symfony/Doctrine Repositories

Doctrine repositories are something the standard developer should know about, but how to really work well with repositories is hard to master without some good thought to it. Benjamin @beberlei wrote a blog post on taming repositories here http://www.whitewashing.de/2013/03/04/doctrine_repositories.html sometime ago. The ending result of its refactor creates several classes that are centered around Specification pattern that has a match method to which it gets passed the query builder, an object that is implementation specific. This creep however is somewhat unavoidable because of doctrine api and how it works. When you change implementation or use another library you have trashed all of this approach once and for all. Since this is very tied to implementation details I proposed a more quick and dirty approach that aims for versatility and could be easily implemented by anyone in less time. It is more deficient and quickly discardable and centers around quicker readable code.

With the previous approach one ends up with a bunch of classes and things like:

<?php
$specification = new Spec\AsArray(
    new Spec\AndX(
        new Spec\FilterGroup($groupId),
        new Spec\FilterPermission($permission)
    )
);
 
$groups = $app['orm.ems']['api']
    ->getRepository('\EasyBib\Api\Entity\Group')
    ->match($specification)
;

What if we make the specification more tuned in a single callable class:

$groups = $app['orm.ems']['api']
    ->getRepository('\EasyBib\Api\Entity\Group')
    ->match(new PowerUsers(15))
;

Then your user repository turns from:

<?php
class UserRepository
{
    public function match(UserSpecification $specification)
    {
        $qb = $this->createQueryBuilder('u');
        $expr = $specification->match($qb, 'u');
 
        $query = $qb->where($expr)->getQuery();
 
        $specification->modifyQuery($query);
 
        return $query->getResult();
    }
}

Into this:

<?php
class UserRepository
{
    public function match(callable $filter)
    {
        $qb = $this->createQueryBuilder('u');
 
        return call_user_func($filter, $qb);
    }
}

And all the things that build the query land into one place that gets encapsulated with a good old name:

<?php
class PowerUsers
{
    protected $limit;
 
    public function __construct($number)
    {
        $this->limit = $number;
    }
 
    public function __invoke(QueryBuilder $qb)
    {
        // here goes all the heavy lifting under one place
    }
}

This approach speaks for versatility, you can still compose reusability via the constructor if you may though the versatile aspect of this leaves the door open for more separation of concerns and composition but gives you the advantage of not spending too much time on prototyping phase. Also with some more sauce on it you can provide adapters for different implementations should you need to change gateways for doctrine odm or orm or other possibly.

I personally found to come up with more descriptive names so that controllers or services get slimmer and more readable.

Please feel free to leave your feedback and retweet this blog post. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *