PHP 5.5+ event layer
observr is a generic event layer that provides a flexible foundation for event handling in a domain-agnostic and non-intrusive way.
Install via cli using composer:
php composer.phar require jgswift/observr:0.2.*
Install via composer.json using composer:
{
"require": {
"jgswift/observr": "0.2.*"
}
}
- php 5.5+
- react/promise
The Subject layer is a flexible interface/trait combination that provides a generic observer pattern implementation.
class User implements observr\SubjectInterface
{
use observr\Subject;
}
$user = new User;
$user->attach("login",function($sender,$e) {
return "login successful";
});
var_dump($user->setState("login")); // returns "login successful"
observr\Event
delivers a combination EventInterface
and EventAwareInterface
to the observing callbacks
and can be used to define pass-thru state variables.
$user = new User;
$user->attach("login",function($sender,$e) {
$e->cancel(); // manual cancellation
});
$event = new observr\Event($user);
$user->setState("login",$event)
var_dump($event->isCanceled()); // returns true
To implement a custom event interface just inherit observr\Event
or implement EventInterface
and EventAwareInterface
State status and exception container
interface EventInterface {
public function isComplete();
public function isSuccess();
public function isFailure();
public function isCanceled();
public function getException();
public function setException(\Exception $exception);
}
Performs state changes for EventInterface
interface EventAwareInterface {
public function cancel(EventInterface $event = null);
public function complete(EventInterface $event);
public function fail(EventInterface $event);
public function succeed(EventInterface $event);
}
In order to handle events differently depending on package behavior it is possible to attach
/detach
with namespaces.
$user = new User;
$user->attach('login',function($sender,$e) {
echo 'default login';
});
$user->attach('login.myNamespace',function($sender,$e) {
echo 'my custom login';
});
$user->setState('login'); // echo 'default login' & 'my custom login'
$user->detach('login.myNamespace'); // removes only the namespaced handler
$user->setState('login'); // echo 'default login'
observr\Event
also implements the observer pattern itself and can be used to
validate event results. The validation constants are COMPLETE
, FAILURE
, SUCCESS
, and CANCEL
COMPLETE
is notified when all observers fire without failureFAILURE
is notified when any observer throws an exceptionSUCCESS
is notified every time the state is changed without failure or cancellationCANCEL
is notifier when any observer invokes cancellation
$user = new User;
$event = new observr\Event($user);
$user->attach("login",function($sender,$e) {
// success with no cancellation or errors, default behavior
});
$event->attach(observr\Event::COMPLETE,function() {
echo 'COMPLETE';
});
$event->attach(observr\Event::SUCCESS,function() {
echo 'SUCCESS';
});
$user->setState("login",$event); // everything worked!
// invokes ...
// COMPLETE
// SUCCESS
$user->attach("login",function($sender,$e) {
$e->cancel(); // cancels the event
});
$event->attach(observr\Event::CANCEL,function() {
echo 'CANCELED';
});
$user->setState("login",$event); // manual cancellation
// invokes ...
// COMPLETE
// CANCEL
$user->attach("login",function($sender,$e) {
throw new \Exception; // causes fault
});
$event->attach(observr\Event::FAILURE,function() {
echo 'FAILURE'
});
$user->setState("login",$event); // throws exception
// invokes ...
// FAILURE
Emitter is a Subject where events are exposed into individual objects
class Button implements observr\SubjectInterface {
use observr\Subject;
}
$button = new Button;
$click = new observr\Emitter('click');
$button->setState($click, function($sender,$e) {
echo 'CLICKED!';
});
$click($button); // prints 'CLICKED'!
interface EmitterInterface extends SubjectInterface {
public function getName();
public function emit($e = null);
public function on(callable $callable);
public function bind(callable $callable);
public function unbind(callable $callable);
public function map(callable $callable);
public function filter(callable $callable);
public function __toString();
}
class Button {
function __construct() {
$this->click = new observr\Emitter('click');
$this->mouseup = new observr\Emitter('mouseup');
$this->mousedown = new observr\Emitter('mousedown');
}
}
$button = new Button;
$combinedClick = $button->click->map(function($sender, $e) {
/* extra mapping */
})->merge($button->mousedown->map(function($sender,$e) {
/* extra mapping */
}))->merge($button->mouseup->map(function($sender,$e) {
/* extra mapping */
}));
$combinedClick($button); // performs click, mousedown & mouseup all together
Filtering allows mapping procedures to be applied selectively
$sending = $button->click
->filter(function($button,$e) {
if($button instanceof Button) { // only changes Button to "Sending..."
return true;
}
return false;
})->map(function($button,$e) {
$button->value = 'Sending...';
});
$sending($button); // triggers click and changes button text to "Sending..."
A stream provides an easy way to wrap around multiple subjects at once and listen to many events.
$bob = new User
$john = new User;
$stream = new observr\Stream('login');
// instruct stream to watch our user login events
$stream->watch($bob);
$stream->watch($john);
$c = 0;
$stream->attach(function($sender,$e=null)use(&$c) {
$c++; // called twice, this is where we intercept the event
});
// open stream
$stream->open();
// trigger some fake logins
$bob->setState('login');
$john->setState('login');
// close stream
$stream->close();
var_dump($c); // 2
interface StreamInterface {
public function close();
public function getSubjects();
public function isOpen();
public function open();
public function watch($pointer);
public function unwatch($pointer);
public function isWatching($pointer);
}
- jgswift/detectr - complex event processor