Loïc Faugeron Technical Blog

Memio validator and linter 22/04/2015

TL;DR: Write constraints to check models integrity. Linter constraints (e.g. syntax error) are already available.

Everyday Memio, the higly opinionated PHP code generation library, progresses toward its stable release.

In this article, we'll have a look at two packages that have been freshly extracted: memio/validator and memio/linter.

Writing constraints

The validator packages provides an easy way to write constraints. Let's write one to check that method arguments are never scalar.

Note: This is one of the principle in object calisthenics: wrap all primitives and string.

<?php

require __DIR__.'/vendor/autoload.php';

use Memio\Validator\Constraint;
use Memio\Validator\Violation\NoneViolation;
use Memio\Validator\Violation\SomeViolation;

class ArgumentCannotBeScalar implements Constraint
{
    public function validate($model)
    {
        $type = $model->getType();
        if (in_array($type, array('array', 'bool', 'callable', 'double', 'int', 'mixed', 'null', 'resource', 'string'), true)) {
            return new SomeViolation(sprintf('Argument "%s" cannot be scalar', $model->getName()));
        }

        return new NoneViolation();
    }
}

Note: Naming constraints after their error message allow for better reability.

The next step is to register our constraint in a validator. Since our constraint aims Argument models, we'll register it in an ArgumentValidator:

// ...

use Memio\Validator\ModelValidator\ArgumentValidator;

$argumentValidator = new ArgumentValidator();
$argumentValidator->add(new ArgumentCannotBeScalar());

When building models, Arguments are burried in Methods, which themselves are burried in Contracts or Objects which in turn are burried in File.

To make things easy, we'd like to simply give the top most model (e.g. File) to a generic Validator. Its responsibility would be to go through each models and execute the appropriate ModelValidator.

In order to do so, we have to create all ModelValidators and assemble them as follow:

// ...

use Memio\Validator\ModelValidator\CollectionValidator;
use Memio\Validator\ModelValidator\ContractValidator;
use Memio\Validator\ModelValidator\FileValidator;
use Memio\Validator\ModelValidator\MethodValidator;
use Memio\Validator\ModelValidator\ObjectValidator;

$collectionValidator = new CollectionValidator();
$methodValidator = new MethodValidator($argumentValidator, $collectionValidator);
$contractValidator = new ContractValidator($collectionValidator, $methodValidator);
$objectValidator = new ObjectValidator($collectionValidator, $methodValidator);
$fileValidator = new FileValidator($contractValidator, $objectValidator);

Finally, we need to create a validator and register our ModelValidators in it:

// ...

use Memio\Validator\Validator;

$calisthenicValidator = new Validator();
$calisthenicValidator->add($argumentValidator);
$calisthenicValidator->add($collectionValidator);
$calisthenicValidator->add($methodValidator);
$calisthenicValidator->add($contractValidator);
$calisthenicValidator->add($objectValidator);
$calisthenicValidator->add($fileValidator);

We can now validate our Models:

// ...

$calisthenicValidator->validate($file); // @throws Memio\Validator\InvalidModelException if one or more constraint fail

The InvalidModelException's message has one line per violation.

Linter

Out of the box, Memio provides a Linter which provides the following constraints:

As we've seen above, constructing and assembling constraints and validators can be quite tiresome.

That's where memio/memio, the main central repository, starts to be useful by providing a simple way to get a ready to use linter:

<?php

require __DIR__.'/vendor/autoload.php';

use Memio\Memio\Config\Build;

$linter = Build::linter();

$linter->validate($file); // @throws Memio\Validator\InvalidModelException if one or more constraint fail

Conclusion

Validator allows the creation of custom constraint to ensure that the build Models are valid. Linter is a set of constraints ready to use, allowing to prevent syntax errors (e.g. a method cannot be both final and abstract).

If you'd like to find out more about Memio Validator, have a look at the documentation: