So I decided to create a trait that I could add to my classes that would add a simple way of binding functions to an event, and to fire those events. It ended up going a little further than I expected it to, and I am getting into things I do not normally work with, such as defining an object via a variable, $a = new $b(), and working with anonymous functions.
I don't really have any friends who program so I guess I am just looking for a second, third, or twentieth pair of eyes to look it over and give me some feedback. Below is the everything needed to create events, and over at https://github.com/mrkmg/phpevents I have a few examples.
event.php:
<?php
trait EventTemplate
{
protected $_event_types = array();
protected $_event_binds = array();
protected $_event_defaults_processed = false;
public function bind($type,$action)
{
$this->_event_check_defaults();
return $this->_event_bind($type,$action);
}
public function unbind($type,$action)
{
$this->_event_check_defaults();
if(!key_exists($action,$this->_event_binds))
return false;
unset($this->_event_binds[$type][array_search($action,$this->_events_binds)]);
return true;
}
public function fire($type)
{
$this->_event_check_defaults();
$this->_event_fire($type);
}
private function _event_bind($type,$action)
{
if(!key_exists($type,$this->_event_types))
{
throw new Exception('Type not defined');
}
if(!isset($this->_event_binds[$type]) || !is_array($this->_event_binds[$type])) $this->_event_binds[$type] = array();
$this->_event_binds[$type][] = $action;
return true;
}
private function _event_check_defaults()
{
if(!$this->_event_defaults_processed) $this->_event_process_defaults();
}
private function _event_set_type($type,$class="Event")
{
$this->_event_types[$type] = $class;
}
private function _event_fire($type)
{
$event = new $this->_event_types[$type]($type,$this);
foreach($this->_event_binds[$type] as $bind)
{
if(is_callable($bind))
$this->_event_fire_closure($bind,$event);
elseif(is_string($bind))
$this->_event_fire_string($bind,$event);
}
}
private function _event_fire_string($string,&$event)
{
call_user_func($string,$event);
}
private function _event_fire_closure($closure,&$event)
{
$closure($event);
}
private function _event_process_defaults()
{
if(isset($this->_event_default_types))
{
foreach($this->_event_default_types as $type=>$class)
{
if(is_int($type))
{
$type = $class;
$class = "Event";
}
$this->_event_set_type($type,$class);
}
}
if(isset($this->_event_default_binds))
{
foreach($this->_event_default_binds as $event=>$methods)
{
foreach($methods as $method)
{
$this->_event_bind($event,function($event) use($method){$this->{$method}($event);});
}
}
}
$this->_event_defaults_processed = true;
}
}
class Event
{
public $type;
public $object;
public $microtime;
public $backtrace;
const PRINT_HTML = 0;
const PRINT_CMD = 1;
public function __construct($type,&$object)
{
$this->type = $type;
$this->object = &$object;
$this->microtime = microtime(true);
$backtrace = debug_backtrace();
array_shift($backtrace);
array_shift($backtrace);
$this->backtrace = $backtrace;
}
/**
* Bill Getas
* http://www.php.net/manual/en/function.debug-backtrace.php#101498
*/
public function print_backtrace($type = self::PRINT_HTML)
{
switch($type)
{
case 0:
array_walk( $this->backtrace,function($a,$b) {print "<br /><b>". basename( $a['file'] ). "</b> <font color=\"red\">{$a['line']}</font> <font color=\"green\">{$a['function']}()</font> -- ". dirname( $a['file'] ). "/";});
break;
case 1:
array_walk( $this->backtrace,function($a,$b) {print "\n".basename( $a['file'] )."\t{$a['line']}\t{$a['function']}()\t".dirname( $a['file'] ). "/";});
break;
default:
throw new Exception('Could not understand type.');
}
}
}
?>
Here is an example of how it could be used for logging
logexample.php
<?php
include('event.php');
class Person {
use EventTemplate;
private $data = array();
private $inited = false;
public function __construct()
{
$this->_event_set_type('get_data');
$this->_event_set_type('save_data');
$this->_event_set_type('write_property');
$this->_event_set_type('delete_data');
}
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
public function __set($name,$value)
{
if (array_key_exists($name, $this->data)) {
$this->data[$name] = $value;
$this->fire('write_property');
return true;
}
return false;
}
public function get($id)
{
//GET info from database
$this->data = array(
'name'=>'Test Person',
'email'=>'test@demo.com',
'username'=>'tperson'
);
$this->inited = true;
//Fire the get_data event
$this->fire('get_data');
return true;
}
public function save()
{
//send data to database
//Fire update_data event
$this->fire('save_data');
}
public function delete()
{
//remove data from database
//Fire delete_data event
$this->fire('delete_data');
}
}
function logEv($event)
{
echo 'Event: '.$event->type.' for user: '.$event->object->username;
$event->print_backtrace(Event::PRINT_HTML);
echo "<br /><br />\n";
}
//make a new person
$person = new Person;
//Bind events to logEv function
$person->bind('get_data','logEv');
$person->bind('save_data','logEv');
$person->bind('delete_data','logEv');
$person->bind('write_property','logEv');
//Load an existing user from db
$person->get('id_of_person');
//Change the users users email
$person->email = 'tperson@domain.com';
//Save the user to the db
$person->save();
//Change the users users username
$person->username = 'testp';
//Save the user to the db
$person->save();
//Delete the user from the db
$person->delete();
?>
This example does not use all the features, but gives you an idea.
_event_*should probably be part of the event class. Which would remove the necessity of the prefix. You've started a profiling session withmicrotime()but never finished it. TIL:debug_backtrace()Though in this context I don't think its necessary. If all you want is the function and its arguments, you can just use__FUNCTION__andfunc_get_args()respectively. I'll give it another whack pending example. – mseancole Jul 19 '12 at 21:13