Thursday, March 23, 2017

Compiling multiple PHP FPM installations on Ubuntu Linux

PHP FPM

Dependencies

In order to use some of the PHP features, we need to have some dependencies installed. Libjpeg, libpng f.e. for GD, but also libmysqlclient for mysql, libcurl and so on. This is a small set ob standard dependencies, that I use for every php installation, so I install this upfront and don't wait for error messages when configuring the php sources.

sudo apt-get install libxml2-dev libzip-dev libbz2-dev libcurl4-openssl-dev  libjpeg-dev libpng12-dev libtidy-dev libevent-dev libjpeg62-dev libmysqlclient-dev

Installation

First of all, you need to download the php sources. Go to php.net and download the sources. I need always at least 3: The newest 5.4 release (5.4.7 as of this writing), 5.3.3 (as this is the minimum php version for Zend Framework 2) and the latest 5.3. release (5.3.17 as of this writing).
I will install PHP under "/opt/php/5.4.7", "/opt/php/5.3.3" and "/opt/php/5.3.17".

cd Downloads
tar xf php-5.4.7.tar.gz
cd php-5.4.7
./configure --prefix=/opt/php/5.4.7 --with-pear --with-zlib --enable-shmop --enable-xml --with-curl --enable-libxml --enable-session --with-pcre-regex --enable-exif --enable-inline-optimization --enable-soap --enable-sockets --with-xmlrpc --enable-mbstring --with-bz2 --with-tidy --enable-dom --with-pdo-sqlite --with-pdo-mysql=shared --with-gd=shared --with-zlib-dir --with-jpeg-dir --enable-fpm
make
make test
sudo make install


Repeat the above steps for every need php installation.
The only really important switch is "--prefix" and "--enable-fpm". The rest are standard options that I want to reuse accross installations.

Known issues

On Ubuntu 12.04.1 I receive the following error while configuring PHP 5.3.3.:
"error: libjpeg.(a|so) not found."

A quick workaround is:
sudo ln -s /usr/lib/x86_64-linux-gnu/libjpeg.a /usr/lib/libjpeg.a
sudo ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /usr/lib/libjpeg.so 
sudo ln -s /usr/lib/x86_64-linux-gnu/libpng.a /usr/lib/libpng.a 
sudo ln -s /usr/lib/x86_64-linux-gnu/libpng.so /usr/lib/libpng.so 

Symlink the binaries

I create a symlink for every installed php installation. Additional, I'll create a "default" symlink to use one as the default php installation. This will be PHP 5.4.7 in this case.
sudo ln -s /opt/php/5.4.7/bin/php /usr/local/bin/php-5.4.7
sudo ln -s /opt/php/5.3.3/bin/php /usr/local/bin/php-5.3.3
sudo ln -s /opt/php/5.3.17/bin/php /usr/local/bin/php-5.3.17

Installing Nginx

Installation

You can download and install nginx using apt:
sudo apt-get install nginx

Configuring Nginx

For reference here is my nginx.conf (/etc/nginx/nginx.conf)
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events {
        worker_connections 768;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        # gzip_vary on;
        # gzip_proxied any;
        zip_comp_level 2;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # nginx-naxsi config
        ##
        # Uncomment it if you installed nginx-naxsi
        ##

        #include /etc/nginx/naxsi_core.rules;

        ##
        # nginx-passenger config
        ##
        # Uncomment it if you installed nginx-passenger
        ##

        #passenger_root /usr;
        #passenger_ruby /usr/bin/ruby;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}
Additional you need to change some settings in your fastcgi_params configuration (/etc/nginx/fastcgi_params)
fastcgi_param   QUERY_STRING            $query_string;
fastcgi_param   REQUEST_METHOD          $request_method;
fastcgi_param   CONTENT_TYPE            $content_type;
fastcgi_param   CONTENT_LENGTH          $content_length;
fastcgi_param   PATH_INFO               $fastcgi_script_name;

fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
fastcgi_param   REQUEST_URI             $request_uri;
fastcgi_param   DOCUMENT_URI            $document_uri;
fastcgi_param   DOCUMENT_ROOT           $document_root;
fastcgi_param   SERVER_PROTOCOL         $server_protocol;

fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;

fastcgi_param   REMOTE_ADDR             $remote_addr;
fastcgi_param   REMOTE_PORT             $remote_port;
fastcgi_param   SERVER_ADDR             $server_addr;
fastcgi_param   SERVER_PORT             $server_port;
fastcgi_param   SERVER_NAME             $server_name;

fastcgi_param   HTTPS                   $https;

# This should be tuned according to your environment
fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors on;

Virtual Hosts

One config to rule them all

Thursday, September 1, 2016

Authorization and Event Sourcing with prooph and ZF2 / ZF3

Authorization with prooph components and event sourced aggregates roots are a common problem to solve. Here is a short explanation on how to do this using Zend\Authentication. First of all, we need an aggregate root class for it. Here is a minimal example with some basic properties.
<?php

declare (strict_types=1);

namespace My\Identity\Model\Identity

class Identity extends \Prooph\EventSourcing\AggregateRoot
{
    /**
     * @var IdentityId
     */
    protected $identityId;

    /**
     * @var EmailAddress
     */
    protected $emailAddress;

    /**
     * @var string
     */
    protected $passwordHash;

    /**
     * @var Role[]
     */
    protected $roles;

    public function login(string $password) : bool
    {
        if (password_verify($password, $this->passwordHash)) {
            $this->recordThat(IdentityLoggedIn::with($this->identityId));

            if (password_needs_rehash($this->passwordHash, PASSWORD_DEFAULT)) {
                $this->rehashPassword($password);
            }

            return true;
        }

        $this->recordThat(IdentityLoginDenied::with($this->identityId));

        return false;
    }

    public function logout()
    {
        $this->recordThat(IdentityLoggedOut::with($this->identityId));
    }

    public function rehashPassword(string $password)
    {
        $passwordHash = password_hash($password, PASSWORD_DEFAULT);

        $this->recordThat(IdentityPasswordWasRehashed::withData(
            $this->identityId,
            $this->passwordHash,
            $passwordHash
        ));
    }

    protected function whenIdentityLoggedIn(IdentityLoggedIn $event)
    {
    }

    protected function whenIdentityLoginDenied(IdentityLoginDenied $event)
    {
    }

    protected function whenIdentityLoggedOut(IdentityLoggedOut $event)
    {
    }

    protected function whenIdentityPasswordWasRehashed(IdentityPasswordWasRehashed $event)
    {
        $this->passwordHash = $event->newPasswordHash();
    }
}
Additionally, we will need a read only version of the aggregate root:
<?php

declare (strict_types=1);

namespace My\Identity\Model\Identity\ReadOnly;

use My\Identity\Model\Identity\IdentityId;
use My\Identity\Model\Identity\Role;
use My\SharedKernel\Model\EmailAddress;
use ZfcRbac\Identity\IdentityInterface;

class Identity implements IdentityInterface
{
    /**
     * @var IdentityId
     */
    protected $identityId;

    /**
     * @var EmailAddress
     */
    protected $emailAddress;

    /**
     * @var Role[]
     */
    protected $roles;

    public function __construct(
        IdentityId $identityId,
        EmailAddress $emailAddress,
        array $roles
    ) {
        Assertion::notEmpty($roles);
        Assertion::allIsInstanceOf($roles, Role::class);

        $this->identityId = $identityId;
        $this->emailAddress = $emailAddress;
        $this->roles = $roles;
    }

    public function identityId() : IdentityId
    {
        return $this->identityId;
    }

    public function emailAddress() : EmailAddress
    {
        return $this->emailAddress;
    }

    /**
     * Get the list of roles of this identity
     *
     * @return string[]
     */
    public function getRoles()
    {
        $roles = [];

        foreach ($this->roles as $role) {
            $roles[] = $role->getName();
        }

        return $roles;
    }

    public static function fromArray(array $data) : Identity
    {
        Assertion::keyExists($data, 'identityId');
        Assertion::keyExists($data, 'emailAddress');
        Assertion::keyExists($data, 'roles');
        Assertion::isArray($data['roles']);
        Assertion::notEmpty($data['roles']);

        return new self(
            IdentityId::fromString($id),
            new EmailAddress($data['emailAddress']),
            $roles
        );
    }
}
And a projector, too:
<?php

declare (strict_types=1);

namespace My\Identity\Projection\Identity;

use Assert\Assertion;
use My\Identity\Model\Identity\Event\IdentityWasCreated;
use My\Identity\Model\Identity\Event\IdentityPasswordWasRehashed;
use Doctrine\MongoDB\Collection;
use Doctrine\MongoDB\Connection;

class IdentityProjector
{
    /**
     * @var Connection
     */
    private $connection;

    /**
     * @var string
     */
    private $dbName;

    /**
     * @param Connection $connection
     * @param string $dbName
     */
    public function __construct(Connection $connection, $dbName)
    {
        Assertion::minLength($dbName, 1);

        $this->connection = $connection;
        $this->dbName = $dbName;
    }

    public function onIdentityWasCreated(IdentityWasCreated $event)
    {
        $roles = [];
        foreach ($event->roles() as $role) {
            $roles[] = $role->getName();
        }

        $data = [
            '_id' => $event->identityId()->toString(),
            'roles' => $roles,
            'emailAddress' => $event->emailAddress()->toString(),
        ];

        $collection = $this->identityReadCollection();

        $collection->insert($data);
    }

    public function onIdentityPasswordWasRehashed(IdentityPasswordWasRehashed $event)
    {
        $this->identityReadCollection()->update(
            [
                '_id' => $event->identityId()->toString(),
            ],
            [
                '$set' => [
                    'passwordHash' => $event->newPasswordHash(),
                ],
            ]
        );
    }

    private function identityReadCollection()
    {
        return $this->connection->selectCollection($this->dbName, 'identity');
    }
}
This is a very simple example, omitting the event classes and value objects. It might be worth adding some additional methods and/ or properties, when needed. The login command simply takes the email address and password as parameters, that's simple enough for us now, so what's needed is a command handler for Login / Logout.
<?php

declare (strict_types=1);

namespace My\Identity\Model\Identity\Handler;

use My\Identity\Model\Identity\Command\Login;
use My\Identity\Model\Identity\Command\Logout;
use Zend\Authentication\Adapter\ValidatableAdapterInterface as AuthAdapter;
use Zend\Authentication\AuthenticationService;

/**
 * Class LoginLogoutHandler
 * @package My\Identity\Model\Identity\Handler
 */
final class LoginLogoutHandler
{
    /**
     * @var AuthenticationService
     */
    private $authenticationService;

    /**
     * @var AuthAdapter;
     */
    private $authAdapter;

    public function __construct(
        AuthenticationService $authenticationService,
        AuthAdapter $authAdapter
    ) {
        $this->authenticationService = $authenticationService;
        $this->authAdapter = $authAdapter;
    }

    public function handleLogin(Login $command)
    {
        $this->authenticationService->clearIdentity();

        $this->authAdapter->setIdentity($command->emailAddress()->toString());
        $this->authAdapter->setCredential($command->password()->toString());

        $auth = $this->authenticationService->authenticate($this->authAdapter);

        if (! $auth->isValid()) {
            throw new \RuntimeException('not authorized');
        }
    }

    public function handleLogout(Logout $command)
    {
        $this->authenticationService->clearIdentity();
    }
}
That should be enough for now. We also need an implementation of Zend\Authentication\Storage\StorageInterface. In this case, we use MongoDB as backend.
<?php

declare (strict_types=1);

namespace My\Identity\Infrastructure;

use Assert\Assertion;
use My\Identity\Model\Identity\ReadOnly\Identity;
use Doctrine\MongoDB\Connection;
use Zend\Authentication\Storage\StorageInterface;

final class AuthenticationStorage implements StorageInterface
{
    /**
     * @var StorageInterface
     */
    private $storage;

    /**
     * @var Connection
     */
    private $connection;

    /**
     * @var string
     */
    private $dbName;

    /**
     * @var mixed
     */
    private $resolvedIdentity;

    /**
     * AuthenticationStorage constructor.
     * @param StorageInterface $storage
     * @param Connection $connection
     * @param string $dbName
     */
    public function __construct(StorageInterface $storage, Connection $connection, $dbName)
    {
        Assertion::minLength($dbName, 1);

        $this->storage = $storage;
        $this->connection = $connection;
        $this->dbName = $dbName;
    }

    /**
     * Returns true if and only if storage is empty
     *
     * @throws \Zend\Authentication\Exception\InvalidArgumentException If it is impossible to determine whether
     * storage is empty or not
     * @return boolean
     */
    public function isEmpty()
    {
        if ($this->storage->isEmpty()) {
            return true;
        }

        $identity = $this->read();

        if ($identity === null) {
            $this->clear();

            return true;
        }

        return false;
    }

    /**
     * Returns the contents of storage
     *
     * Behavior is undefined when storage is empty.
     *
     * @throws \Zend\Authentication\Exception\InvalidArgumentException If reading contents from storage is impossible
     * @return mixed
     */
    public function read()
    {
        if (null !== $this->resolvedIdentity) {
            return $this->resolvedIdentity;
        }

        $identity = $this->connection->selectCollection($this->dbName, 'identity')->findOne([
            '_id' => $this->storage->read()
        ]);

        if (! $identity) {
            $this->resolvedIdentity = null;

            return;
        }

        $this->resolvedIdentity = Identity::fromArray($identity);

        return $this->resolvedIdentity;
    }

    /**
     * Writes $contents to storage
     *
     * @param  mixed $contents
     * @throws \Zend\Authentication\Exception\InvalidArgumentException If writing $contents to storage is impossible
     * @return void
     */
    public function write($contents)
    {
        $this->resolvedIdentity = null;
        $this->storage->write($contents);
    }

    /**
     * Clears contents from storage
     *
     * @throws \Zend\Authentication\Exception\InvalidArgumentException If clearing contents from storage is impossible
     * @return void
     */
    public function clear()
    {
        $this->resolvedIdentity = null;
        $this->storage->clear();
    }
}
Next we need an implementation of Zend\Authentication\Adapter\ValidatableAdapterInterface:
<?php

declare (strict_types=1);

namespace My\Identity\Infrastructure;

use Assert\Assertion;
use My\Identity\Model\Identity\IdentityCollection;
use My\Identity\Model\Identity\IdentityId;
use My\SharedKernel\Model\StringLiteral;
use Doctrine\MongoDB\Connection;
use MongoRegex;
use Zend\Authentication\Adapter\AbstractAdapter;
use Zend\Authentication\Result;

final class ZendMongoDbAuthAdapter extends AbstractAdapter
{
    /**
     * @var IdentityCollection
     */
    private $identityCollection;

    /**
     * @var Connection
     */
    private $connection;

    /**
     * @var string
     */
    private $dbName;

    /**
     * $authenticateResultInfo
     *
     * @var array
     */
    private $authenticateResultInfo = null;

    /**
     * ZendMongoDbAuthAdapter constructor.
     * @param IdentityCollection $identityCollection
     * @param Connection $connection
     * @param string $dbName
     */
    public function __construct(
        IdentityCollection $identityCollection,
        Connection $connection,
        $dbName
    ) {
        Assertion::minLength($dbName, 1);
        $this->identityCollection = $identityCollection;
        $this->connection = $connection;
        $this->dbName = $dbName;
    }

    /**
     * Performs an authentication attempt
     *
     * @return \Zend\Authentication\Result
     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface If authentication cannot be performed
     */
    public function authenticate()
    {
        $this->authenticateResultInfo = [
            'code'     => Result::FAILURE,
            'identity' => $this->identity,
            'messages' => []
        ];

        $collection = $this->connection->selectCollection($this->dbName, 'identity');

        $resultIdentities = $collection->find(
            [
                'emailAddress' => new MongoRegex('/^' . $this->getIdentity() . '$/i')
            ],
            [
                '_id' => true
            ]
        )->toArray();

        if (($authResult = $this->authenticateValidateResultSet($resultIdentities)) instanceof Result) {
            return $authResult;
        }

        $identity = current($resultIdentities);

        return $this->authenticateValidateResult($identity);
    }

    /**
     * authenticateValidateResultSet() - This method attempts to make
     * certain that only one record was returned in the resultset
     *
     * @param  array $resultIdentities
     * @return bool|\Zend\Authentication\Result
     */
    private function authenticateValidateResultSet(array $resultIdentities)
    {
        if (count($resultIdentities) < 1) {
            $this->authenticateResultInfo['code']       = Result::FAILURE_IDENTITY_NOT_FOUND;
            $this->authenticateResultInfo['messages'][] = 'A record with the supplied identity could not be found.';

            return $this->authenticateCreateAuthResult();
        } elseif (count($resultIdentities) > 1) {
            $this->authenticateResultInfo['code']       = Result::FAILURE_IDENTITY_AMBIGUOUS;
            $this->authenticateResultInfo['messages'][] = 'More than one record matches the supplied identity.';

            return $this->authenticateCreateAuthResult();
        }

        return true;
    }

    /**
     * Creates a Zend\Authentication\Result object from the information that
     * has been collected during the authenticate() attempt.
     *
     * @return Result
     */
    private function authenticateCreateAuthResult()
    {
        return new Result(
            $this->authenticateResultInfo['code'],
            $this->authenticateResultInfo['identity'],
            $this->authenticateResultInfo['messages']
        );
    }

    /**
     * authenticateValidateResult() - This method attempts to validate that
     * the record in the resultset is indeed a record that matched the
     * identity provided to this adapter.
     *
     * @param  array $resultIdentity
     * @return Result
     */
    private function authenticateValidateResult($resultIdentity)
    {
        $identity = $this->identityCollection->get(IdentityId::fromString($resultIdentity['_id']));

        if (! $identity) {
            $this->authenticateResultInfo['code']       = Result::FAILURE_IDENTITY_NOT_FOUND;
            $this->authenticateResultInfo['messages'][] = 'Supplied identity not found.';

            return $this->authenticateCreateAuthResult();
        }

        if (! $identity->login(new StringLiteral($this->getCredential()))) {
            $this->authenticateResultInfo['code']       = Result::FAILURE_CREDENTIAL_INVALID;
            $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.';

            return $this->authenticateCreateAuthResult();
        }

        $this->authenticateResultInfo['code']       = Result::SUCCESS;
        $this->authenticateResultInfo['identity']   = $identity->identityId()->toString();
        $this->authenticateResultInfo['messages'][] = 'Authentication successful.';

        return $this->authenticateCreateAuthResult();
    }
}
Now we need two little factories to create our infrastructure:
<?php

declare (strict_types=1);

namespace My\Identity\Container\Infrastructure;

use My\Identity\Infrastructure\AuthenticationStorage;
use Interop\Container\ContainerInterface;
use Zend\Authentication\Storage\Session;

final class AuthenticationStorageFactory
{
    public function __invoke(ContainerInterface $container) : AuthenticationStorage
    {
        $dbName = $container->get('Config')['projection_database'];

        return new AuthenticationStorage(
            new Session(),
            $container->get('doctrine_mongo_connection'),
            $dbName
        );
    }
}
and this one:
<?php

declare (strict_types=1);

namespace My\Identity\Container\Infrastructure;

use My\Identity\Infrastructure\AuthenticationStorage;
use Interop\Container\ContainerInterface;
use Zend\Authentication\AuthenticationService;

final class AuthenticationServiceFactory
{
    public function __invoke(ContainerInterface $container) : AuthenticationService
    {
        return new AuthenticationService($container->get(AuthenticationStorage::class));
    }
}
Last thing we need to do, is configure the service manager accordingly:
<?php
return [
    'factories' => [
        \My\Identity\Infrastructure\AuthenticationStorage::class => \My\Identity\Container\Infrastructure\AuthenticationStorageFactory::class,
        \Zend\Authentication\AuthenticationService::class => \My\Identity\Container\Infrastructure\AuthenticationServiceFactory::class,
        // for prooph's guard plugins:
        \Prooph\ServiceBus\Plugin\Guard\RouteGuard::class => \Prooph\ServiceBus\Container\Plugin\Guard\RouteGuardFactory::class,
        \Prooph\ServiceBus\Plugin\Guard\FinalizeGuard::class => \Prooph\ServiceBus\Container\Plugin\Guard\FinalizeGuardFactory::class,
        \Prooph\ServiceBus\Plugin\Guard\AuthorizationService::class => \Prooph\ServiceBusZfcRbacBridge\Container\ZfcRbacAuthorizationServiceBridgeFactory::class,
    ],
];
So when I did not forget anything, that's it! With the last 3 lines of service manager config, you can even use prooph's ServiceBus ZFC-RBAC-bridge

Friday, February 26, 2016

Flywheel Adapter for ProophEvent-Store 1.0.0 released

Prooph's Event-Store components now also has a Flywheel Adapter.

Flywheel is a serverless document database which only uses flat files on your local filesystem to store the data. All the events will be stored and loaded from a choosen directory. This is well suited when you bootstrap an application and you don't need a real database server right away. It can also be a good candidate for writing functionnal tests.

But of course you must not run it in production since it is not designed to handle a huge amount of events and doesn't manage transactions.

Shout-out to Matthieu Moquet (@MattKetmo) for providing this adapter.

Inheritance with Aggregate Roots in ProophEvent-Store

If you want to make inheritance work with aggregate roots using a common repository for all subtypes, this can be achieved very easily. You need the latest ProophEvent-Store v6.1 to do this.

An example


Consider the following use case:

<?php
abstract class User extends \Prooph\EventSourcing\AggregateRoot
{
    protected $name;

    protected $email;

    public function name()
    {
        return $this->name;
    }

    public function email()
    {
        return $this->email;
    }

    protected function whenUserWasRegisterd(UserWasRegisterd $event)
    {
        $this->name = $event->name();
        $this->email = $event->email();
    }
}

class Admin extends User
{
    public static function register($name, $email)
    {
        $self = new self();
        $self->recordThat(UserWasRegisterd::withData('admin', $name, $email);

        return $self;
    }
}

class Member extends User
{
    public static function register($name, $email)
    {
        $self = new self();
        $self->recordThat(UserWasRegisterd::withData('member', $name, $email);

        return $self;
    }
}
So in order to make this work, you need 3 small changes in your application.

Step 1: Create a UserAggregateTranslator


<?php
final class UserAggregateTranslator extends \Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator
{
    /**
     * @param \Prooph\EventStore\Aggregate\AggregateType $aggregateType
     * @param \Iterator $historyEvents
     * @return object reconstructed AggregateRoot
     */
    public function reconstituteAggregateFromHistory(
        \Prooph\EventStore\Aggregate\AggregateType $aggregateType, 
        \Iterator $historyEvents
    ) {
        $aggregateRootDecorator = $this->getAggregateRootDecorator();

        $firstEvent = $historyEvents->current();
        $type = $firstEvent->type();

        if ($type === 'admin') {
            return $aggregateRootDecorator->fromHistory(Admin::class, $historyEvents);
        } elseif ($type === 'member') {
            return $aggregateRootDecorator->fromHistory(Member::class, $historyEvents);
        }
    }
}

Step 2: Change the assertion method in the EventStoreUserCollection


<?php
final class EventStoreUserCollection extends 
    \Prooph\EventStore\Aggregate\AggregateRepository
{
    public function add(User $user)
    {
        $this->addAggregateRoot($user);
    }
    public function get(UserId $userId)
    {
        return $this->getAggregateRoot($userId->toString());
    }
    protected function assertAggregateType($eventSourcedAggregateRoot)
    {
        \Assert\Assertion::isInstanceOf($eventSourcedAggregateRoot, User::class);
    }
}

Step 3: Make use of your custom AggregateTranslator


<?php
final class EventStoreUserCollectionFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new EventStoreUserCollection(
            $container->get(EventStore::class),
            AggregateType::fromAggregateRootClass(User::class),
            new UserAggregateTranslator()
        );
    }
}

If you use the provided container factory (\Prooph\EventStore\Container\Aggregate\AbstractAggregateRepositoryFactory) then you can also just change the aggregate_translator key in your config to point to the new UserAggregateTranslator and register the UserAggregateTranslator in your container.

Friday, November 13, 2015

PHP-CS-Fixer in PHPStorm with File Watcher

First of all, install php-cs-fixer:


wget http://get.sensiolabs.org/php-cs-fixer.phar -O php-cs-fixer
sudo chmod a+x php-cs-fixer
sudo mv php-cs-fixer /usr/local/bin/php-cs-fixer



Second have a project-specific php-cs config file. As an example take a look here.


In PHPStorm click "File" -> "Settings" -> "Tools" -> "File Watchers"

Fill out the provided view:

Name: PHP CS Fixer
Description: fixes php code style
Show console: Error
File type: PHP
Scope: Project Files
Program: /usr/local/bin/php-cs-fixer
Arguments: --config-file=$ProjectFileDir$/.php_cs fix $FileDir$/$FileName$ -v --diff --dry-run
Working directory: $ProjectFileDir$

It should look like this:

PHPStorm Settings


Hint: You can also ommit the --dry-run option, to let PHP-CS-Fixer automatically fix your code.

Thursday, October 29, 2015

Adding Custom Metadata to Domain Events with Prooph Event Store

When you record events with the Prooph EventStore only the payload (the event data that was recorded by your aggregate roots) gets recorded. If you use the EventStore together with the Prooph ServiceBus and enable the TransactionManager from the EventStoreBusBridge, some additional metadata gets recorded, too. That is the "causation_id" and the "causation_name" (the command id that triggered this event and the command name).

In a real-word application you might want to record some additional metadata, like "who" issued the command, what was his IP address, which user-agent did he use, and so on. This is especially useful when you want to know, which commands "John Doe" sent to the system. The simplest way to achieve this, is to use an EventStore-Plugin. Here is an example for Zend Framework 2, using the Zend\Authentication component to get the issuer and Zend\Http\PhpEnvironment\RemoteAddress to get the client's IP address.

<?php

namespace My\App\EventStore;

use ArrayIterator;
use Iterator;
use Prooph\Common\Event\ActionEvent;
use Prooph\EventStore\EventStore;
use Prooph\EventStore\Plugin\Plugin;
use Prooph\EventStore\Stream\Stream;
use Zend\Authentication\AuthenticationServiceInterface;
use Zend\Http\PhpEnvironment\RemoteAddress;

/**
 * Class EventEnricher
 * @package My\App\EventStore
 */
final class EventEnricher implements Plugin
{
    /**
     * @var AuthenticationServiceInterface
     */
    private $authenticationService;

    /**
     * @param AuthenticationServiceInterface $authenticationService
     */
    public function __construct(AuthenticationServiceInterface $authenticationService)
    {
        $this->authenticationService = $authenticationService;
    }

    /**
     * @param EventStore $eventStore
     */
    public function setUp(EventStore $eventStore)
    {
        $eventStore->getActionEventEmitter()->attachListener('create.pre', [$this, 'onEventStoreCreateStream'], -1000);
        $eventStore->getActionEventEmitter()->attachListener('appendTo.pre', [$this, 'onEventStoreAppendToStream'], -1000);
    }

    /**
     * This method takes domain events as argument which are going to be added to the event stream and
     * adds the issued_by (user id), ip_address and user_agent as metadata to each event.
     *
     * @param Iterator $recordedEvents
     * @return Iterator
     */
    private function handleRecordedEvents(Iterator $recordedEvents)
    {
        if ($this->authenticationService->hasIdentity()) {
            $issuer = $this->authenticationService->getIdentity()->getId();
        } else {
            $issuer = 'guest';
        }

        $clientIp = $this->findClientIp();
        $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

        $enrichedRecordedEvents = [];

        foreach ($recordedEvents as $recordedEvent) {
            $recordedEvent = $recordedEvent->withAddedMetadata('issued_by', $issuer);
            $recordedEvent = $recordedEvent->withAddedMetadata('ip_address', $clientIp);
            $recordedEvent = $recordedEvent->withAddedMetadata('user_agent', $userAgent);

            $enrichedRecordedEvents[] = $recordedEvent;
        }

        return new ArrayIterator($enrichedRecordedEvents);
    }

    /**
     * Add event metadata on event store createStream
     *
     * @param ActionEvent $createEvent
     */
    public function onEventStoreCreateStream(ActionEvent $createEvent)
    {
        $stream = $createEvent->getParam('stream');

        if (! $stream instanceof Stream) {
            return;
        }

        $streamEvents = $stream->streamEvents();
        $streamEvents = $this->handleRecordedEvents($streamEvents);

        $createEvent->setParam('stream', new Stream($stream->streamName(), $streamEvents));
    }

    /**
     * Add event metadata on event store appendToStream
     *
     * @param ActionEvent $appendToStreamEvent
     */
    public function onEventStoreAppendToStream(ActionEvent $appendToStreamEvent)
    {
        $streamEvents = $appendToStreamEvent->getParam('streamEvents');
        $streamEvents = $this->handleRecordedEvents($streamEvents);

        $appendToStreamEvent->setParam('streamEvents', $streamEvents);
    }

    /**
     * Find client ip if client was behind proxy
     *
     * @return string
     */
    private function findClientIp()
    {
        $clientIp = new RemoteAddress();
        $clientIp->setUseProxy(true);
        $ip = $clientIp->getIpAddress();

        return $ip;
    }
}

This will add "issued_by", "ip_address" and "user_agent" to the metadata of all recorded events. Now you still need to enable the plugin. If you use the provided container-factories, you simply add the plugin there:

<?php

return [
    'event_store' => [
        'plugins' => [
            \My\App\EventStore\EventEnricher::class,
        ],
    ]
];

Keep in mind, that the domain model is unaware of the domain event's metadata. Therefore if you need to know the issuer f.e. in your domain model, you should write this as a CommandBus-Plugin that manipulates the command. This way you can get the issuer from the command in your command handler within the domain. An example:

<?php

namespace My\App\EventStore;

use ArrayIterator;
use My\App\Commanding\Command;
use Iterator;
use Prooph\Common\Event\ActionEvent;
use Prooph\Common\Event\ActionEventEmitter;
use Prooph\Common\Event\ActionEventListenerAggregate;
use Prooph\Common\Event\DetachAggregateHandlers;
use Prooph\EventStore\EventStore;
use Prooph\EventStore\Stream\Stream;
use Prooph\ServiceBus\CommandBus;
use Zend\Authentication\AuthenticationServiceInterface;
use Zend\Http\PhpEnvironment\RemoteAddress;

/**
 * Class CommandAndEventEnricher
 * @package My\App\EventStore
 */
final class CommandAndEventEnricher implements ActionEventListenerAggregate
{
    use DetachAggregateHandlers;

    /**
     * @var EventStore
     */
    private $eventStore;

    /**
     * @var AuthenticationServiceInterface
     */
    private $authenticationService;

    /**
     * @param EventStore $eventStore
     * @param AuthenticationServiceInterface $authenticationService
     */
    public function __construct(EventStore $eventStore, AuthenticationServiceInterface $authenticationService)
    {
        $this->eventStore = $eventStore;
        $this->authenticationService = $authenticationService;
        $this->eventStore->getActionEventEmitter()->attachListener('create.pre', [$this, 'onEventStoreCreateStream'], -1000);
        $this->eventStore->getActionEventEmitter()->attachListener('appendTo.pre', [$this, 'onEventStoreAppendToStream'], -1000);
    }

    /**
     * @param ActionEventEmitter $dispatcher
     */
    public function attach(ActionEventEmitter $emitter)
    {
        //Attach with a low priority, so that a potential message translator has done its job already
        $this->trackHandler($emitter->attachListener(CommandBus::EVENT_INITIALIZE, [$this, 'onInitialize'], -1000));
    }

    /**
     * Add the issuer id to the command
     *
     * @param ActionEvent $actionEvent
     */
    public function onInitialize(ActionEvent $actionEvent)
    {
        $command = $actionEvent->getParam(CommandBus::EVENT_PARAM_MESSAGE);

        if (! $command instanceof Command) {
            return;
        }

        if ($this->authenticationService->hasIdentity()) {
            $issuer = $this->authenticationService->getIdentity()->getId();
        } else {
            $issuer = 'guest';
        }

        $command = $command->withIssuedBy($issuer);

        $actionEvent->setParam(CommandBus::EVENT_PARAM_MESSAGE, $command);
    }

    /**
     * This method takes domain events as argument which are going to be added to the event stream and
     * adds the issued_by (user id), ip_address and user_agent as metadata to each event.
     *
     * @param Iterator $recordedEvents
     * @return Iterator
     */
    private function handleRecordedEvents(Iterator $recordedEvents)
    {
        if ($this->authenticationService->hasIdentity()) {
            $issuer = $this->authenticationService->getIdentity()->getId();
        } else {
            $issuer = 'guest';
        }

        $clientIp = $this->findClientIp();
        $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

        $enrichedRecordedEvents = [];

        foreach ($recordedEvents as $recordedEvent) {
            $recordedEvent = $recordedEvent->withAddedMetadata('issued_by', $issuer);
            $recordedEvent = $recordedEvent->withAddedMetadata('ip_address', $clientIp);
            $recordedEvent = $recordedEvent->withAddedMetadata('user_agent', $userAgent);

            $enrichedRecordedEvents[] = $recordedEvent;
        }

        return new ArrayIterator($enrichedRecordedEvents);
    }

    /**
     * Add event metadata on event store createStream
     *
     * @param ActionEvent $createEvent
     */
    public function onEventStoreCreateStream(ActionEvent $createEvent)
    {
        $stream = $createEvent->getParam('stream');

        if (! $stream instanceof Stream) {
            return;
        }

        $streamEvents = $stream->streamEvents();
        $streamEvents = $this->handleRecordedEvents($streamEvents);

        $createEvent->setParam('stream', new Stream($stream->streamName(), $streamEvents));
    }

    /**
     * Add event metadata on event store appendToStream
     *
     * @param ActionEvent $appendToStreamEvent
     */
    public function onEventStoreAppendToStream(ActionEvent $appendToStreamEvent)
    {
        $streamEvents = $appendToStreamEvent->getParam('streamEvents');
        $streamEvents = $this->handleRecordedEvents($streamEvents);

        $appendToStreamEvent->setParam('streamEvents', $streamEvents);
    }

    /**
     * Find client ip if client was behind proxy
     *
     * @return string
     */
    private function findClientIp()
    {
        $clientIp = new RemoteAddress();
        $clientIp->setUseProxy(true);
        $ip = $clientIp->getIpAddress();

        return $ip;
    }
}

In this case you need to add the enricher as CommandBus-Plugin instead of an EventStore-Plugin of course:

<?php

return [
    'service_bus' => [
        'command_bus' => [
            'plugins' => [
                \Prooph\EventStoreBusBridge\TransactionManager::class,
                \My\App\EventStore\CommandAndEventEnricher::class
            ],
        ],
    ],
];


You noticed perhaps that we need to use a special command class that knows the issuer. That is easy to achieve:

<?php

namespace My\App\Commanding;

use Assert\Assertion;
use Prooph\Common\Messaging\Command as ProophCommand;
use Prooph\Common\Messaging\PayloadConstructable;
use Prooph\Common\Messaging\PayloadTrait;

/**
 * Class Command
 * @package My\App\Commanding
 */
abstract class Command extends ProophCommand implements PayloadConstructable
{
    use PayloadTrait;

    /**
     * Returns a new instance of the message with given version
     *
     * @param string $issuer
     * @return Command
     */
    public function withIssuedBy($issuer)
    {
        Assertion::string($issuer);

        $messageData = $this->toArray();

        $messageData['issued_by'] = $issuer;

        return static::fromArray($messageData);
    }

    /**
     * @return string
     */
    public function issuedBy()
    {
        return $this->metadata['issued_by'];
    }
}


Now you only need to extend your commands from this base-class.

Note: If you work with Prooph EventStore, you don't need to extends the base-classes from Prooph, like Command. You're free to implement them by yourself for your needs.

Wednesday, October 28, 2015

Introducing the prooph-components for DDD, EventSourcing & CQRS in PHP

Prooph EventStore v6.0 beta 1 is now available with the final version coming along very soon. Time to write a short blogpost about what's new in it.

- History Replay
- Snapshot Support
- Apply Events Late
- EventStream Iterator
- interop config support
- AggregateRepository Factory

History Replay

The read model can be regenerated from history at any point in time by replaying recorded events.

Snapshot Support

If you have way to many events to replay in order to reconstruct the aggregate root, why not take a snapshot? The prooph components ship with different snapshot adapters like MongoDB, Doctrine DBAL and even Memcached backends. So replaying of thousands of events is not a burden any more.

Apply Events Late

Events are only applied after transaction commit to ensure that the aggregate root can never reach an invalid state. This is especially useful for long-running CLI scripts.

EventStream iterator

The event stream is now implemented as an Iterator instead of a simple array. This reduces the memory usage a lot when replaying big event streams.

Interop config support

The components ship with ready-to-use container-interop factories using https://github.com/sandrokeil/interop-config. This makes it really simple to configure the factories.

Want more? Visit getprooph.org to check out the documentation or try out our demo application proophessor-do. Pick up a task and at the sample application to get started with event sourcing and get your hands a little dirty.