Last active
August 29, 2015 13:56
-
-
Save joshuaadickerson/9045673 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
CREATE TABLE {$db_prefix}plugin_hooks ( | |
id_hook smallint(6) unsigned NOT NULL auto_increment, | |
id_plugin smallint(6) unsigned NOT NULL, | |
event varchar(100) NOT NULL, | |
class varchar(100) NOT NULL DEFAULT '', | |
method varchar(255) NOT NULL, | |
include_file varchar(100) NOT NULL DEFAULT '', | |
priority tinyint(4) NOT NULL DEFAULT 0, | |
PRIMARY KEY (id_hook) | |
) ENGINE=MyISAM; | |
*/ | |
// @todo store the results in the event. | |
// Initially I was thinking that $dispathcer->dispatch() should return the results, | |
// but then we need to allow an event to not be stopped. We shouldn't allow that | |
// If an event is stopped by a listener, it should be required to be recreated | |
// to run again. So, if we are doing that, just store the results in it as well. | |
class Event | |
{ | |
protected $name; | |
protected $parameters = array(); | |
protected $dispatcher; | |
/** | |
* The DIC | |
* @var Pimple | |
*/ | |
protected $container; | |
protected $stop = false; | |
protected $stopped_by; | |
public function __construct($name, array $parameters = array()) | |
{ | |
if (!is_string($name) || trim($name) === '') | |
{ | |
throw new \InvalidArgumentException('The name of the event must not be empty'); | |
} | |
$this->name = $name; | |
if (!empty($parameters)) | |
{ | |
$this->setParameters = $parameters; | |
} | |
} | |
public function setParameters(array $parameters) | |
{ | |
foreach ($parameters as $param => $value) | |
{ | |
$this->parameters[$param] = $value; | |
} | |
return $this; | |
} | |
public function getParameter($param) | |
{ | |
if (isset($this->parameters[$param])) | |
{ | |
return $this->parameters[$param]; | |
} | |
} | |
public function getParameters() | |
{ | |
return $this->parameters; | |
} | |
public function getName() | |
{ | |
return $this->name; | |
} | |
public function trigger() | |
{ | |
if (empty($this->dispatcher)) | |
{ | |
throw new \RuntimeException('The Event Dispatcher has not been set.'); | |
} | |
return $this->dispatcher->dispatch($this); | |
} | |
public function stopPropogation() | |
{ | |
$this->stop = true; | |
return $this; | |
} | |
public function isStopped() | |
{ | |
return $this->stop; | |
} | |
public function setStoppedBy(EventListener $listener) | |
{ | |
$this->stopped_by = $listener; | |
return $this; | |
} | |
public function getStoppedBy() | |
{ | |
return $this->stopped_by; | |
} | |
/** | |
* Makes it so that the events can be self-triggering | |
* @param $dispatcher | |
*/ | |
public function setEventDispatcher($dispatcher) | |
{ | |
$this->dispatcher = $dispatcher; | |
return $this; | |
} | |
public function setContainer($container) | |
{ | |
$this->container = $container; | |
return $this; | |
} | |
} | |
class EventListener extends BaseEntity | |
{ | |
protected $id_hook; | |
protected $id_plugin; | |
protected $event; | |
protected $class; | |
protected $method; | |
protected $include; | |
protected $priority; | |
} | |
class EventDispatcher | |
{ | |
protected $_events = array(); | |
// Get the replacements for the paths | |
protected $_path_replacements = array( | |
'BOARDDIR' => BOARDDIR, | |
'SOURCEDIR' => SOURCEDIR, | |
'EXTDIR' => EXTDIR, | |
'LANGUAGEDIR' => LANGUAGEDIR, | |
'ADMINDIR' => ADMINDIR, | |
'CONTROLLERDIR' => CONTROLLERDIR, | |
'SUBSDIR' => SUBSDIR, | |
); | |
protected $_debug = false; | |
protected $_included_files = array(); | |
protected $container; | |
public function __construct($container, array $event_subscribers) | |
{ | |
$this->container = $container; | |
foreach ($event_subscribers as $event) | |
{ | |
$this->registerListener($event); | |
} | |
// @todo I think this is legacy | |
if (!empty($GLOBALS['settings']['theme_dir'])) | |
$this->path_replacements['$themedir'] = $GLOBALS['settings']['theme_dir']; | |
} | |
public function setDebug($debug) | |
{ | |
$this->debug = (bool) $debug; | |
} | |
public function dispatch(Event $event) | |
{ | |
$name = $event->getName(); | |
// Track that it was called | |
if ($this->debug) | |
{ | |
// @todo this should have the debugger injected instead | |
$GLOBALS['context']['debug']['hooks'][] = $name; | |
} | |
if (isset($this->_events[$name])) | |
{ | |
// Add the container to the event | |
$event->setContainer($this->container) | |
->setEventDispatcher($this); | |
$results = $this->callListeners($this->_events[$name]); | |
} | |
return $results; | |
} | |
protected function callListeners(Event $event, array $listeners) | |
{ | |
$return = array(); | |
foreach ($listeners as $priority_level) | |
{ | |
foreach ($priority_level as $listener) | |
{ | |
$this->includeFile($listener->include); | |
$return[] = $this->call($listener->class, $listener->method, $event); | |
// Someone wants us to stop propogating this event | |
if ($event->isStopped()) | |
{ | |
$event->setStoppedBy($listener); | |
break; | |
} | |
} | |
if ($event->isStopped()) | |
{ | |
break; | |
} | |
} | |
return $return; | |
} | |
protected function call($class, $method, Event $event) | |
{ | |
// No class, just a function | |
if (empty($class)) | |
{ | |
if (is_callable($method)) | |
return call_user_func(array($class, $method), $event); | |
// @todo throw exception? | |
return; | |
} | |
// No method, but a class - call the constructor | |
if (empty($method)) | |
{ | |
if (is_class($class)) | |
return new $class($event); | |
// @todo throw exception? | |
return; | |
} | |
// An instantiated class | |
if (is_callable(array($class, $method))) | |
return call_user_func(array($class, $method), $event); | |
// A static call | |
if (is_callable($class . '::' . $method)) | |
return call_user_func($class . '::' . $method, $event); | |
} | |
/** | |
* | |
* @param string $file | |
*/ | |
protected function includeFile($file) | |
{ | |
if (empty($file)) | |
{ | |
return; | |
} | |
$filename = $this->getFileName($file); | |
if (isset($this->_included_files[$filename])) | |
{ | |
return; | |
} | |
$this->_included_files[$filename] = true; | |
// Don't check if it exists because we want it to break if it doesn't | |
// @todo maybe not? | |
require_once($filename); | |
} | |
protected function getFileName($file) | |
{ | |
return strtr($file, $this->_path_replacements); | |
} | |
public function registerListener(EventListener $event) | |
{ | |
$name = $event->getName(); | |
$priority = $event->getPriority(); | |
if (!isset($this->_events[$name])) | |
{ | |
$this->_events[$name] = array(); | |
} | |
if (!isset($this->_events[$name][$priority])) | |
{ | |
$this->_events[$name][$priority] = array(); | |
// Sort by priority | |
usort($this->_events[$name], array($this, 'priority_sort')); | |
} | |
$this->_events[$name][$priority][] = $event; | |
return $this; | |
} | |
/** | |
* Sort callback for the priority | |
* | |
* @param array $a | |
* @param array $b | |
* @return int | |
*/ | |
protected function priority_sort($a, $b) | |
{ | |
$a_priority = (int) $a['priority']; | |
$b_priority = (int) $b['priority']; | |
if ($a_priority === $b_priority) | |
{ | |
return 0; | |
} | |
return ($a_priority < $b_priority) ? -1 : 1; | |
} | |
/** | |
* Checks that an event will work | |
* Checks that the file (if it is set) exists | |
* Checks if the callable is callable (uses autoloader) | |
* | |
* @param EventListener $listener | |
*/ | |
public function checkEventListener(EventListener $listener) | |
{ | |
if ($listener->include !== null) | |
{ | |
if (!file_exists($listener->include)) | |
{ | |
throw new \Exception('The include file "' . $listener->include . '" does not exist'); | |
} | |
$this->includeFile($listener->file); | |
} | |
} | |
public function getAndDispatch($name, array $parameters = array()) | |
{ | |
$event = new Event($name, $parameters); | |
$this->dispatch($event); | |
return $event; | |
} | |
} | |
class PluginManager extends BaseManager | |
{ | |
public function __construct($database, $cache, EventDispatcher $dispatcher) | |
{ | |
$this->database = $database; | |
$this->cache = $cache; | |
$this->dispatcher = $dispatcher; | |
} | |
public function removeListener($event_id) | |
{ | |
} | |
public function addListener(EventListener $event) | |
{ | |
} | |
public function addPlugin(PluginInterface $plugin) | |
{ | |
// Make sure it has a name | |
// Get the date and set the $date_installed | |
// Insert it | |
// Get the event listeners | |
// Set the plugin id for the listeners | |
// Insert the listeners | |
} | |
public function removePlugin($plugin_id) | |
{ | |
// Iterate all of the listeners | |
// Remove them by plugin_id | |
// Remove the plugin from the database | |
// Remove the plugin file | |
} | |
} | |
/** | |
* This is just an example | |
*/ | |
class FooPlugin extends Entity | |
{ | |
protected $name; | |
protected $installed; | |
protected $disabled; | |
protected $date_installed; | |
public function getEventListeners() | |
{ | |
$listeners = array(); | |
$event = new EventListener; | |
$event->setEvent('topic.get') | |
->setClass('Foo') | |
->setMethod('setBar') | |
->setPriority(33); | |
$listeners[] = $event; | |
return $listeners; | |
} | |
} | |
// Usage // | |
// We used to do: | |
call_integration_hook('integrate_prepare_display_context', array(&$output, &$message)); | |
// Now we do | |
$event = new Event('display.prepare_context', array('output' => $output, 'message' => $message)); | |
$container['events']->dispatch($event); | |
// You can also do | |
$event->trigger(); | |
// You can even do | |
$results = $event->trigger()->getResults(); | |
// Call the helper method | |
$results = $app['events']->getAndDispatch('display.prepare_context', array('output' => $output, 'message' => $message))->getResults(); | |
// @todo this needs to use __get() and the ArrayObject | |
$event->output; | |
// The name is changed so that you can better find it. | |
// You know it is in the display controller | |
// It starts with a noun and ends with a verb | |
// Removed the "integrate_" stuff | |
// I'd like to make certain methods in the container global so you don't first use | |
// the key. So it would be $container->dispatch() or $container->event($name, $parameters)->trigger()->getResults() | |
// You should still be able to use references |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment