Doctrine Simple Mapping and Repository Compiler Pass

Recently I stumble upon some doctrine repository definition situation where there were many definitions of custom doctrine repositories. Instead of having a long list over and over of a custom doctrine service definition I started to look at simpler ways this can be just done and let that adapt within a compiler pass and be customized further down the road as much specific to the application as possible.

I checked out https://github.com/mmoreram/SimpleDoctrineMapping and even though this package seems to be interesting to simplify the auto-mapping in some way I wanted to really have a way to do the simplification not for the mapping of the entities but for the custom repository service definitions. One good thing about this library is that is not a bundle! yey!

So I ended up with just passing an array of parameters, with keys being the model classes, and the values being the repository fqcn’s.

<?php
 
namespace Vendor\Package\DependencyInjection\Compiler;
 
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
 
class CustomRepositoryPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        foreach ($container->getParameter('repositories') as $model => $class) {
            $definition = new Definition();
            $definition
                ->setClass($class)
                ->setArguments([$model])
                ->setFactoryMethod('getRepository')
                ->setFactoryService('doctrine')
                ->setPublic(true)
                ->setLazy(true)
            ;
 
            $container->setDefinition(
                'vendor.repository.'.$this->slug($class),
                $definition
            );
        }
    }
 
    private function slug($fqcn)
    {
        $fqcn = str_replace('Repository', '', $fqcn);
        $names = explode('\\', $fqcn);
        $string = end($names);
        $string = preg_replace('/([A-Z])/', '_$1', $string);
 
        return strtolower(substr($string,1));
    }
}

This as a first approach is simple and straight to the point. Now my 100’s of lines on service definitions for custom repositories were downed to just specifying the key value pairs as parameters:

parameters:
    repositories:
        Vendor\Package\Model\Action: Vendor\Package\Repository\ActionRepository
        Vendor\Package\Model\Tenant: Vendor\Package\Repository\TenantRepository
        // ...

This compiler pass can be used as i have shown in previous posts without the need of a bundle fortunately! And it can be further taylor to meet the domain needs of your domain packages.

Although in early stage I just wanted to put it out so it could benefit someone with the same problem. Take it just as an initial idea and then improve upon it like I am doing in other projects.

Encouragements, thanks for reading. Appreciated!

Code Nuances, Important to Who Hears/Reads

This is a long promised post.

Let’s start.

What is wrong with the following picture?

$subject        = $actionManager->findOrCreateComponent($user);
$someMorelonger = $actionManager->execute($subject);

First, the brain look for differences to understand what is being written. You don’t see a book aligning their similar *Howevers* across pages. What you see is in a poem the font is well taken care and the procedures administered to markup based on syntax.
Aligning equals in this case makes it harder to maintain. Suppose you add a third line, that has a longer property name, now you need to change three lines instead of one. Makes no sense right?

Second, the brain works reading from left to right and from top down. You don’t gain anything by indenting weirdly spaces like this before an equal sign. It is code, poetry, not a senseless computer matrix.

The other example aligned with this mindset is. What happens when the word is too long like here:

    "phpunit/phpunit":                      "~4.1.4",
    "matthiasnoback/symfony-service-definition-validator": "~1.2.0",
    "raulfraile/ladybug": "~1.0.8"

All plot to have neatly aligned versions is thrown to the trash can! Just don’t waste time doing this is my advice.

Let’s see another of this well known hassles:

<?php
 
namespace Bafford\PasswordStrengthBundle\Validator\Constraints;
 
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
 
class PasswordStrengthValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
        if($value === null)
            $value = '';
 
        if($constraint->minLength > 0 && (strlen($value) < $constraint->minLength))
            $this->context->addViolation($constraint->tooShortMessage, array('{{length}}' => $constraint->minLength));
 
        if($constraint->requireLetters && !preg_match('/\pL/', $value))
            $this->context->addViolation($constraint->missingLettersMessage);
 
        if($constraint->requireCaseDiff && !preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/', $value))
            $this->context->addViolation($constraint->requireCaseDiffMessage);
 
        if($constraint->requireNumbers && !preg_match('/\pN/', $value))
            $this->context->addViolation($constraint->missingNumbersMessage);
    }
}

This time in a known password strength bundle. Notice the hassle in maintaining these conditional without braces. It just makes it harder to read and maintain. If one makes a mistake there, hunting the bug could be very tedious and cryptic.

The importance of use for a cs fixer tool cannot be underestimated. For a code that is changing continually, error prone, introducing an error happens at a fast rate. Therefore we should be more careful and integrate the fixer into the cycle.

Picture now simple code turned into a nightmare:

function it_does_the_job();

Into:

/**
 * It does the job.
 * It does some very complicated stuff, but gets the job done.
 *
 * @author Luis Cordova <cordoval@gmail.com>
 * @author Some other author <etc@gmail.com>
 * @package my package
 * @license MIT
 *
 * @param void
 * @return void
 */
public function it_does_the_job();

It just does not make sense when git and services like github et al help us find who did what in a project.

Let’s jump to another nuance, this time is getting into the custom of locking the composer to 3 digits. Some people do:

"jms/di-extra-bundle": "1.4.*",
"jms/aop-bundle": "1.0.*"

Instead of:

"jms/di-extra-bundle": "1.4.1",
"jms/aop-bundle": "1.0.1"

Or similar. The thing is sometimes when they just do 1.4 or ~1.4 some have the wrong idea. You should read this https://igor.io/2013/01/07/composer-versioning.html. However an improvement on top of it is the custom to specify 3 numbers like in:

"jms/di-extra-bundle": "~1.4.1",

This is a better practice since it allows only the minor number to sweep. It does not leave any doubt as to which number is flexible and helps avoiding some undesired scenarios like:

Hm, why 2.2.1 are older then 2.1 ?
I use this format in composer
 
"apy/datagrid-bundle": "~2.1"
And i have 2.2.1 version

:), then you wonder!

There are more nuances but i am shipping this blog to help today rather than later!