2017 © Pedro Peláez
 

project ammit

[DDD] Stable framework agnostic Command resolver

image

imediafrance/ammit

[DDD] Stable framework agnostic Command resolver

  • Saturday, March 10, 2018
  • by gmorel
  • Repository
  • 5 Watchers
  • 2 Stars
  • 752 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 2 Forks
  • 7 Open issues
  • 8 Versions
  • 0 % Grown

The README.md

, (*1)

Ammit

PHP7 DDD v1.0.0 beta v2.0.0 never SemVer Build Status Code Quality Code Coverage Dependency Status, (*2)

A light, stable and framework agnostic Command resolver library, (*3)

Currently being Battle Tested (not yet 1.0.0 tagged)

  1. What the lib does
  2. How to use it ?
  3. What the lib does not ?
  4. Why ?
  5. How does it work ?
  6. Pragmatic ?
  7. Want to contribute ?
  8. Ammit ?

A Command is a simple well named DTO reflecting user intention., (*4)

Consequently it shall be immutable., (*5)

, (*6)

  • RegisterUserCommand
  • DisableUserCommand
  • BookCargoCommand

What the lib does ?

  • It provides a helper to easily extract scalar data from a PSR-7 HTTP Request (or a CLI input) in order to instantiate an immutable Command.
  • It allows to implement clean Commands (no public field).
  • It is designed to be a simple UI Validation framework dependency free.
  • It is designed to ease segregating UI validation Vs Domain validation concerns

Simple Spec, (*7)

How to use it ?

composer require ammit-php/ammit, (*8)

Example:, (*9)

Implement a RegisterUserCommandResolver which will map a PSR-7 ServerRequestInterface into a RegisterUserCommand. Before creating RegisterUserCommand it will perform a UI validation., (*10)

RegisterUserController.php, (*11)

$registerUserCommandResolver = new RegisterUserCommandResolver();
try {
    $command = $registerUserCommandResolver->resolve($request);
} catch (AbstractNormalizableCommandResolverException $e) {
    // Return a JSON error following jsonapi.org's format
    // @see http://jsonapi.org/examples/#error-objects-basics
    return JsonResponse::fromJsonString(
        json_encode(
            $e->normalize()
        ), 
        406
    );
}

try {
    $this->userService->registerUser($command);
} catch(DomainException $e) {
   // ...
}
// ...

RegisterUserCommandResolver.php, (*12)

/**
 * Resolve a PSR-7 Request into a RegisterUserCommand (Data Transfer Object)
 */
class RegisterUserCommandResolver extends AbstractPureCommandResolver
{
    /**
     * @inheritdoc
     */
    public function resolve(ServerRequestInterface $request): RegisterUserCommand
    {
        $commandConstructorValues = $this->resolveRequestAsArray($request);

        // We are using variadic function here (https://wiki.php.net/rfc/variadics)
        return new RegisterUserCommand(...$commandConstructorValues);
    }

    /**
     * @inheritDoc
     */
    protected function validateThenMapAttributes(ServerRequestInterface $request): array
    {
        // $id = $_GET['id']
        $id = $this->queryStringValueValidator->mustBeString(
            $request,
            'id'
        );

        // $firstName = $_POST['firstName']
        $firstName = $this->attributeValueValidator->mustBeString(
            $request,
            'firstName'
        );

        // $lastName = $_POST['lastName']
        $lastName = $this->attributeValueValidator->mustBeString(
            $request,
            'lastName'
        );

        // $email = $_POST['email']
        $email = $this->attributeValueValidator->mustBeString(
            $request,
            'email'
        );

        // Will be injected directly in RegisterUserCommand::__construct(...$args)
        // as variadic function
        $commandConstructorValues = [
            $id,
            $firstName,
            $lastName,
            $email
        ];

        return $commandConstructorValues;
    }
}

Use it with Symfony: http://symfony.com/doc/current/request/psr7.html, (*13)

Use it with Laravel: TBA, (*14)

Public API

Pure extending AbstractPureCommandResolver
Raw $_GET $_POST
$this->rawValueValidator $this->queryStringValueValidator $this->attributeValueValidator
Boolean ->mustBeBoolean(...)
String ->mustBeString(...)
Integer ->mustBeInteger(...)
Float ->mustBeFloat(...)
Array ->mustBeArray(...)
Date ->mustBeDate(...)
DateTime ->mustBeDateTime(...)
Pragmatic extending AbstractPragmaticCommandResolver
Raw $_GET $_POST
$this->rawValueValidator $this->queryStringValueValidator $this->attributeValueValidator
Same as AbstractPureCommandResolver
UUID ->mustBeUuid(...)
Length ->mustHaveLengthBetween(...)
Email ->mustBeEmailAddress(...)
Regex ->mustBeValidAgainstRegex(...)

What the lib does not ?

  • It is not designed to be a Symfony Form Component replacement.
  • It is not designed to create complex validation. It's aim is to validate simple scalar. Yet it still allows "pragmatic" complex UI validation for prototyping/RAD.
  • It is not designed to use PHP reflection. It is only meant to use Command constructor.

Why ?

We were using Symfony Form Component to map and validate HTTP Requests to our Commands., (*15)

But it was way too complex and hacky. And too tempting to put our Domain validation into FormType. Then to "forget" to put it back into our Domain., (*16)

Furthermore we wanted to anticipate Immutable class., (*17)

How does it work ?

Complete Spec, (*18)

It is using \Closure internally in order to be able to catch all \Exception. Otherwise it would display only 1 validation issue. And we want to see all validation issues at once like with Forms., (*19)

Pragmatic ?

You may have needs to put some Domain validation in your UI. Sometimes we need to do some Rapid Application Development when prototyping. And to take shortcuts knowing we will have to pay back our technical debt in a near future., (*20)

With Ammit you would use our AbstractPragmaticCommandhenResolver (Pragmatic) instead of our AbstractPureCommandResolver (Pure) helper. It will allow you to use more complex validation like uuid validation for example:, (*21)

$email = $attributeValueValidator->mustBeUuid(
    $request,
    'id'
);

A validation is missing. You can still inject your own., (*22)

Want to contribute ?

Read UBIQUITOUS_LANGUAGE_DICTIONARY.md, (*23)

Init docker container: docker-compose up -d, (*24)

Composer install: docker-compose run --rm composer install, (*25)

Use container: docker/bin/php -v (first do chmod +x docker/bin/php), (*26)

Add Unit Test then: docker/bin/php bin/atoum, (*27)

Ammit ?

Ammit, an ancient egyptian goddess involved in heart weighting. She was devouring souls of human judged to be not pure enough to continue their voyage towards Osiris and immortality., (*28)

The Versions

10/03 2018

dev-master

9999999-dev

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

04/05 2017

v1.0.0-beta5

1.0.0.0-beta5

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

03/05 2017

v1.0.0-beta4

1.0.0.0-beta4

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

02/05 2017

v1.0.0-beta3

1.0.0.0-beta3

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

27/04 2017

v1.0.0-beta2

1.0.0.0-beta2

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

19/03 2017

v1.0.0-beta

1.0.0.0-beta

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

07/03 2017

dev-feature/dependancy_free_1

dev-feature/dependancy_free_1

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires

05/02 2017

v0.9.0

0.9.0.0

[DDD] Stable framework agnostic Command resolver

  Sources   Download

MIT

The Requires

 

The Development Requires