Provides an event API

Installs: 4 684

Dependents: 11

Watchers: 1

Language: PHP

v1.3.3 2015-07-11 16:51 UTC

README

Release Build Status HHVM Code Quality Code Coverage Packagist

The API provided by the Event package allows developers to provide hooks which other developers may hook into, to be notified when certain events occur inside the application and take action.

Inside ICanBoogie, events are often used to alter initial parameters, take action before/after an operation is processed or when it fails, take action before/after a request is dispatched or to rescue an exception.

Feature highlights

  • Easily implementable.
  • Events are typed.
  • Events are fired as they are instantiated.
  • Events usually have a target object, but simpler event types can also be emitted.
  • Event hooks are attached to classes rather than objects, and they are inherited.
  • Event hooks can be attached to a finish chain that is executed after the event hooks chain.
  • Execution of the event chain can be stopped.

A twist on the Observer pattern

The pattern used by the API is similar to the Observer pattern, although instead of attaching event hooks to objects they are attached to their class. When an event is fired upon a target object, the hierarchy of its class is used to filter event hooks.

Consider the following class hierarchy:

ICanBoogie\Operation
└─ ICanBoogie\SaveOperation
    └─ Icybee\Modules\Node\SaveOperation
        └─ Icybee\Modules\Content\SaveOperation
            └─ Icybee\Modules\News\SaveOperation

When the process event is fired upon a Icybee\Modules\News\SaveOperation instance, all event hooks attached to the classes for this event are called, starting from the event hooks attached to the instance class (Icybee\Modules\News\SaveOperation) all the way up to those attached to its root class.

Thus, event hooks attached to the Icybee\Modules\Node\SaveOperation class are called when the process event is fired upon a Icybee\Modules\News\SaveOperation instance. One could consider that event hooks are inherited.

Typed events

An instance of an Event subclass is used to provide contextual information about an event to the event hooks processing it. It is passed as the first argument, with the target object as second argument (if any). This instance contain information directly relating to the type of event they accompany.

For example, a process event is usually accompanied by a ProcessEvent instance, and a process:before event—fired before a process event—is usually accompanied by a BeforeProcessEvent instance. Here after is the definition of the ProcessEvent class for the process event type, which is fired on ICanBoogie\Operation instances:

<?php

namespace ICanBoogie\Operation;

/**
 * Event class for the `ICanBoogie\Operation::process` event.
 */
class ProcessEvent extends \ICanBoogie\Event
{
    /**
     * Reference to the response result property.
     *
     * @var mixed
     */
    public $rc;

    /**
     * The response object of the operation.
     *
     * @var \ICanBoogie\HTTP\Response
     */
    public $response;

    /**
     * The request that triggered the operation.
     *
     * @var \ICanBoogie\HTTP\Request
     */
    public $request;

    /**
     * The event is constructed with the type `process`.
     *
     * @param \ICanBoogie\Operation $target
     * @param array $payload
     */
    public function __construct(\ICanBoogie\Operation $target, array $payload)
    {
        parent::__construct($target, 'process', $payload);
    }
}

Event types

The event type is usually the name of an associated method. For example, the process event type is fired after the ICanBoogie\Operation::process method was called, and the process:before event type is fired before.

Namespacing and naming

Event classes should be defined in a namespace unique to their target object. Events targeting ICanBoogie\Operation instances should be defined in the ICanBoogie\Operation namespace.

The class name should match the event type. ProcessEvent for the process event type, BeforeProcessEvent for the process:before event.

Firing events

Events are fired simply by instantiating an event class.

The following example demonstrates how the process event is fired upon an ICanBoogie\Operation instance:

<?php

namespace ICanBoogie;

class Operation
{
    // …

    public function __invoke()
    {
        // …

        $response->rc = $this->process();

        new Operation\ProcessEvent($this, array('rc' => &$response->rc, 'response' => $response, 'request' => $request)); 

        // …
    }

    // …
}

Note that before events can be emitted the event collection to use must be defined. This is done by patching the get() method of the Events class:

<?php

use ICanBoogie\Events:

$events = new Events(array(

    'ICanBoogie\Operation::process' => array
    (
        'my_callback'
    )

));

Events::patch('get', function() use($events) { return $events; });

Using this technique you could also patch the get() method and create the collection just in time.

Attaching event hooks

Event hooks are attached using the attach() method of an event collection. The attach() method is smart enough to create the event type from the parameters type. In the following example, the event hook is attached to the ICanBoogie\Operation::process:before event type.

<?php

use ICanBoogie\Operation;

$events->attach(function(Operation\BeforeProcessEvent $event, Operation $operation) {

    // …

}); 

Attaching an event hook to a specific target

Using the attach_to() method, an event hook can be attached to a specific target, and is only invoked for that target.

<?php

use ICanBoogie\Routing\Controller;

…

$events->attach_to($controller, function(Controller\ActionEvent $event, Controller $target) {

    echo "invoked!";

});

$controller_clone = clone $controller;

new Controller\ActionEvent($controller_clone, …);   // nothing happens
new Controller\ActionEvent($controller, …);         // echo "invoked!"

Attaching an event hook that is to be used once

The once() method attaches event hooks that are automatically detached after they have been used.

<?php

$n = 0;

$events->once('flash', function() use(&n) {

    $n++;

});

new Event(null, 'flash');
new Event(null, 'flash');
new Event(null, 'flash');

echo $n;   // 1

Attaching event hooks using the hooks config

With ICanBoogie, the hooks config can be used to define event hooks.

The following example demonstrate how a website can attach hooks to be notified when nodes are saved (or nodes subclasses), and when an authentication exception is thrown during the dispatch of a request.

<?php

// config/hooks.php

return array
(
    'events' => array
    (
        'Icybee\Modules\Nodes\SaveOperation::process' => 'Website\Hooks::on_nodes_save',
        'ICanBoogie\AuthenticationRequired::rescue' => 'Website\Hooks::on_authentication_required_rescue'
    )
);

Attaching event hooks to the finish chain

The finish chain is executed after the event chain was traversed without being stopped.

The following example demonstrates how an event hook can be attached to the finish chain of the count event to obtain the string "0123". If the third event hook was defined like the others we would obtain "0312".

<?php

class CountEvent extends \ICanBoogie\Event
{
    public $count;

    public function __construct($count)
    {
        $this->count = $count;

        parent::__construct(null, 'count', array());
    }
}

$events->attach('count', function(CountEvent $event) {

    $event->count .= 2;

});

$events->attach('count', function(CountEvent $event) {

    $event->count .= 1;

});

$events->attach('count', function(CountEvent $event) {

    $event->chain(function(CountEvent $event) {

        $event->count .= 3;

    });
});

$event = new CountEvent(0);

echo $event->count; // 0123

Breaking an event hooks chain

The processing of an event hooks chain can be broken by an event hook using the stop() method:

<?php

use ICanBoogie\Operation;

function on_event(Operation\ProcessEvent $event, Operation $operation)
{
    $event->rc = true;
    $event->stop();
}

ICanBoogie auto-config

The package supports the auto-config feature of the framework ICanBoogie and provides a config constructor as well as a lazy getter for the events property:


$core = new ICanBoogie\Core($auto_config);

$core->configs['events']; // obtain the "events" config.
$core->events;            // obtain an Events instance created with the "events" config.

Note: This feature is only available for ICanBoogie 2.x.

Requirements

The package requires PHP 5.4 or later.

Installation

The recommended way to install this package is through Composer:

$ composer require icanboogie/event

Cloning the repository

The package is available on GitHub, its repository can be cloned with the following command line:

$ git clone https://github.com/ICanBoogie/Event.git

Documentation

The package is documented as part of the ICanBoogie framework documentation. You can generate the documentation for the package and its dependencies with the make doc command. The documentation is generated in the build/docs directory. ApiGen is required. The directory can later be cleaned with the make clean command.

The following classes are documented:

Testing

The test suite is ran with the make test command. PHPUnit and Composer need to be globally available to run the suite. The command installs dependencies as required. The make test-coverage command runs test suite and also creates an HTML coverage report in build/coverage. The directory can later be cleaned with the make clean command.

The package is continuously tested by Travis CI.

Build Status Code Coverage

License

icanBoogie/event is licensed under the New BSD License - See the LICENSE file for details.