- Basic Concepts
- Files and Directory Layout
- Request Processing Overview
- The Application Object
- Modules
- Generating a Response
- URL Handlers
- Requests
- Responses
- Filters and Layout
- Dependency Injection
- Static Resources
- Common Patterns
- Settings
- Sessions
- Handling Logins
- Sendables
- Encryption
- Deploying Resources
- Asset Storage
- Logging
- Handling Exceptions
- Dates and Times
- Events
- Record Streams
- Modules and Scaffolds
- Http Clients
- Toolkit Classes
- StringTools
- Mime
- Xml
- Custard
- Introduction & Setup
- Creating a custard command
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.
});