Contents

Events

PHP does not provide events as a first class language feature. However using closures we can achieve much the same thing.

Rhubarb provides two strategies for implementing event handling: the EventEmitter trait and the Event object.

EventEmitter

The EventEmitter which you can use in any class to add event dispatching support to any existing object.

Dispatching Events

Firstly use the trait on your class:

class MyClass
{
    use EventEmitter;
}

Now to raise an event simply call the raiseEvent function. Pass any arguments you wish to the listeners. For example here we raise an event called SomethingInterestingHappened and pass two arguments to any listener.

class MyClass
{
    use EventEmitter;

    private function somethingInteresting()
    {
        $arg1 = "foo";
        $arg2 = "bar";

        $this->raiseEvent("SomethingInterestingHappened", $arg1, $arg2);
    }
}

Listening to events

Simply call the public attachEventHandler() method of the object that raises events and pass the name of the event to listen to and an anonymous function to be used as a callback. The callback can define arguments that will receive the values passed when the event was raised.

$myClass = new MyClass();
$myClass->attachEventHandler("SomethingInterestingHappened", function($arg1, $arg2){
    // Have fun with $arg1 and $arg2 here...
});

Returning Values

An event handling callback can return a value. The first listener to an event to return a value will itself be returned as the result of raiseEvent to the event emitter.

class MyClass
{
    use EventEmitter;

    private function somethingInteresting()
    {
        $arg1 = "foo";
        $arg2 = "bar";

        $arg3 = $this->raiseEvent("SomethingInterestingHappened", $arg1, $arg2);
        // $arg3 will be foobar - see handler below.
    }
}

$myClass = new MyClass();
$myClass->attachEventHandler("SomethingInterestingHappened", function($arg1, $arg2){
    return $arg1.$arg2;
});

Sometimes you need to allow all listeners to return a value. This is achieved by passing another closure as the last argument when calling raiseEvent. If passed this closure will be called with the return value from each listener:

class MyClass
{
    use EventEmitter;

    private function somethingInteresting()
    {
        $arg1 = "foo";
        $arg2 = "bar";

        $this->raiseEvent("SomethingInterestingHappened", $arg1, $arg2, function($response){
            // This response callback will be called twice; once for each handler below.
        });
    }
}

$myClass = new MyClass();
$myClass->attachEventHandler("SomethingInterestingHappened", function($arg1, $arg2){
    return $arg1.$arg2;
});
$myClass->attachEventHandler("SomethingInterestingHappened", function($arg1, $arg2){
    return $arg2.$arg1;
});

Clearing and replacing event handlers

To remove all event handlers from an emitter simply call clearEventHandlers(). This is protected so must be called from within the emitter.

To detach a single event handler you can call the public detachEventHandler($eventName). This removes all the handlers for that event.

$myClass->detachEventHandler("SomethingInterestingHappened");

You can replace all other handlers with a new one by calling replaceEventHandler():

$myClass->replaceEventHandler("SomethingInterestingHappened", function(){
    // New handler replaces all others...
});

Using constants for event names

As event names are strings it is quite common to find an event handler isn't firing because of a simple misspelling or perhaps someone has renamed the emitted event. A good practice is to use constants instead of string literals to make sure the IDE can assist you in getting the right event name. The constant should be defined in the emitting class:

class MyClass
{
    use EventEmitter;

    const EVENT_SOMETHING_INTERESTING_HAPPENED = "SomethingInterestingHappened";

    private function somethingInteresting()
    {
        $arg1 = "foo";
        $arg2 = "bar";

        $this->raiseEvent(self::EVENT_SOMETHING_INTERESTING_HAPPENED, $arg1, $arg2, function($response){
            // This response callback will be called twice; once for each handler below.
        });
    }
}

$myClass = new MyClass();
$myClass->attachEventHandler(MyClass::EVENT_SOMETHING_INTERESTING_HAPPENED, function($arg1, $arg2){
    return $arg1.$arg2;
});

The Event object

One down side of the EventEmitter is that event names are simple strings often leading to simple errors where events aren't hooked up properly because of misspellings or refactoring excercises gone wrong. This can be mitigated to some degree by using constants for event names but a developer still can't easily see what events are emitted by your object.

A more formal approach to events uses the Event object.

Define the event property

For each event you plan to raise you should add a public named property and initialise this to an instance of 'Event':

class MyClass
{
    /**
    * @var Event
    */
    public $splinesReticulated;

    public function __construct()
    {
        $this->splinesReticulated = new Event();
    }
}

Attach a handler

Simply call the attachHandler() method on the event property and pass a callback function to call when the event is triggered.

$object = new MyClass();
$object->splinesReticulated->attachHandler(function(){
    // Do something now the splines have reticulated
});

Raise the event

Call the event's raise() method to raise the event to all attached handlers:

class MyClass
{
    /**
    * @var Event
    */
    public $splinesReticulated;

    public function __construct()
    {
        $this->splinesReticulated = new Event();
    }

    public function reticulateSplines()
    {
        // Reticulate the splines
        $this->splinesReticulated->raise();
    }

}

Arguments, return values and response call backs.

These operate in exactly the same way as for the EventEmitter. You can pass any number of arguments, receive a return value (first handler to do so) and pass a callback function as the final argument which is passed to handlers so that all handlers can return value.s

$myEvent->attachHandler(function($arg1, $arg2){
    return "First handler return value";
});

$myEvent->attachHandler(function($arg1, $arg2){
    return "Second handler return value";
});

$myEvent->raise("arg 1 value", "arg 2 value", function($response){
    // This call back is called for each return value from the handlers above.
});