Skip to content
Snippets Groups Projects
Commit 25f6447e authored by Jared Hancock's avatar Jared Hancock
Browse files

Simple mockup for publish/subscribe signal model

parent f1711751
No related branches found
No related tags found
No related merge requests found
<?php
/*********************************************************************
class.signal.php
Simple interface for a publish and subscribe signal model
Jared Hancock <jared@osticket.com>
Copyright (c) 2006-2013 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
/**
* Signals implement a simple publish/subscribe event model. To keep things
* simplistic between classes and to maintain compatible with PHP version 4,
* signals will not be explicitly defined or registered. Instead, signals
* are connected to callbacks via a string signal name.
*
* The system is proofed with a static inspection test which will ensure
* that for every given Signal::connect() function call, somewhere else in
* the codebase there exists a Signal::send() for the same named signal.
*/
class Signal {
/**
* Subscribe to a signal.
*
* Signal::connect('user.auth', 'function');
*
* The subscribed function should receive a two arguments and will have
* this signature:
*
* function callback($object, $data);
*
* Where the $object argument is the object originating the signal, and
* the $options is a hash-array of other information originating from-
* and pertaining to the signal.
*
* The value of the $data argument is not defined. It is signal
* specific. It should be a hash-array of data; however, no runtime
* checks are made to ensure such an interface.
*
* Optionally, if $object is a class and is passed into the ::connect()
* method, only instances of the named class or subclass will actually
* be connected to the callable function.
*/
/*static*/ function connect($signal, $callable, $object=null) {
global $_subscribers;
if (!isset($_subscribers[$signal])) $_subscribers[$signal] = array();
// XXX: Ensure $object if set is a class
if ($object && !is_string($object))
trigger_error("Invalid object: $object: Expected class");
$_subscribers[$signal][] = array($object, $callable);
}
/**
* Publish a signal.
*
* Signal::send('user.login', $this, array('username'=>'blah'));
*
* All subscribers to the signal will be called in the order they
* connect()ed to the signal. Subscribers do not have the opportunity to
* interrupt or discontinue delivery of the signal to other subscribers.
* The $object argument is required and should almost always be ($this).
* Its interpretation is the object originating or sending the signal.
* It could also be interpreted as the context of the signal.
*
* $data if sent should be a hash-array of data included with the signal
* event. There is otherwise no definition for what should or could be
* included in the $data array. The received data is received by
* reference and can be passed to the callable by reference if the
* callable is defined to receive it by reference. Therefore, it is
* possible to propogate changes in the signal handlers back to the
* originating context.
*/
/*static*/ function send($signal, $object, &$data=null) {
global $_subscribers;
if (!isset($_subscribers[$signal]))
return;
foreach ($_subscribers[$signal] as $sub) {
list($s, $callable) = $sub;
if ($s && !is_a($object, $s))
continue;
call_user_func($callable, $data);
}
}
}
$_subscribers = array();
?>
......@@ -501,6 +501,8 @@ class Staff {
$this->updateTeams($vars['teams']);
$this->reload();
Signal::send('model.modified', $this);
return true;
}
......@@ -520,6 +522,8 @@ class Staff {
db_query('DELETE FROM '.TEAM_MEMBER_TABLE.' WHERE staff_id='.db_input($id));
}
Signal::send('model.deleted', $this);
return $num;
}
......@@ -613,9 +617,14 @@ class Staff {
//Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement.
if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id())
$session->destroy($sid);
Signal::send('auth.login.succeeded', $user);
return $user;
}
Signal::send('auth.login.failed', null, array('username'=>$username,
'password'=>$passwd));
//If we get to this point we know the login failed.
$_SESSION['_staff']['strikes']+=1;
......@@ -637,8 +646,10 @@ class Staff {
}
function create($vars, &$errors) {
if(($id=self::save(0, $vars, $errors)) && $vars['teams'] && ($staff=Staff::lookup($id)))
if(($id=self::save(0, $vars, $errors)) && $vars['teams'] && ($staff=Staff::lookup($id))) {
$staff->updateTeams($vars['teams']);
Signal::send('model.created', $staff);
}
return $id;
}
......
<?php
require_once "class.test.php";
class SignalsTest extends Test {
var $name = "Signals checks";
/**
* Ensures that each signal subscribed to has a sender somewhere else
*/
function testFindSignalPublisher() {
$scripts = $this->getAllScripts();
$matches = $published_signals = array();
foreach ($scripts as $s)
if (preg_match_all("/^ *Signal::send\('([^']+)'/m",
file_get_contents($s), $matches, PREG_SET_ORDER))
foreach ($matches as $match)
$published_signals[] = $match[1];
foreach ($scripts as $s) {
if (preg_match_all("/^ *Signal::connect\('([^']+)'/m",
file_get_contents($s), $matches,
PREG_OFFSET_CAPTURE|PREG_SET_ORDER) > 0) {
foreach ($matches as $match) {
$match = $match[1];
if (!in_array($match[0], $published_signals))
$this->fail(
$s, self::line_number_for_offset($s, $match[1]),
"Signal '{$match[0]}' is never sent");
else
$this->pass();
}
}
}
}
function line_number_for_offset($filename, $offset) {
$lines = file($filename);
$bytes = $line = 0;
while ($bytes < $offset) {
$bytes += strlen(array_shift($lines));
$line += 1;
}
return $line;
}
}
return 'SignalsTest';
?>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment