502 lines
14 KiB
PHP
Executable File
502 lines
14 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Phergie
|
|
*
|
|
* PHP version 5
|
|
*
|
|
* LICENSE
|
|
*
|
|
* This source file is subject to the new BSD license that is bundled
|
|
* with this package in the file LICENSE.
|
|
* It is also available through the world-wide-web at this URL:
|
|
* http://phergie.org/license
|
|
*
|
|
* @category Phergie
|
|
* @package Phergie
|
|
* @author Phergie Development Team <team@phergie.org>
|
|
* @copyright 2008-2010 Phergie Development Team (http://phergie.org)
|
|
* @license http://phergie.org/license New BSD License
|
|
* @link http://pear.phergie.org/package/Phergie
|
|
*/
|
|
|
|
/**
|
|
* Handles on-demand loading of, iteration over, and access to plugins.
|
|
*
|
|
* @category Phergie
|
|
* @package Phergie
|
|
* @author Phergie Development Team <team@phergie.org>
|
|
* @license http://phergie.org/license New BSD License
|
|
* @link http://pear.phergie.org/package/Phergie
|
|
*/
|
|
class Phergie_Plugin_Handler implements IteratorAggregate, Countable
|
|
{
|
|
/**
|
|
* Current list of plugin instances
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $plugins;
|
|
|
|
/**
|
|
* Paths in which to search for plugin class files
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $paths;
|
|
|
|
/**
|
|
* Flag indicating whether plugin classes should be instantiated on
|
|
* demand if they are requested but no instance currently exists
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $autoload;
|
|
|
|
/**
|
|
* Phergie_Config instance that should be passed in to any plugin
|
|
* instantiated within the handler
|
|
*
|
|
* @var Phergie_Config
|
|
*/
|
|
protected $config;
|
|
|
|
/**
|
|
* Phergie_Event_Handler instance that should be passed in to any plugin
|
|
* instantiated within the handler
|
|
*
|
|
* @var Phergie_Event_Handler
|
|
*/
|
|
protected $events;
|
|
|
|
/**
|
|
* Name of the class to use for iterating over all currently loaded
|
|
* plugins
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $iteratorClass = 'Phergie_Plugin_Iterator';
|
|
|
|
/**
|
|
* Constructor to initialize class properties and add the path for core
|
|
* plugins.
|
|
*
|
|
* @param Phergie_Config $config configuration to pass to any
|
|
* instantiated plugin
|
|
* @param Phergie_Event_Handler $events event handler to pass to any
|
|
* instantiated plugin
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct(
|
|
Phergie_Config $config,
|
|
Phergie_Event_Handler $events
|
|
) {
|
|
$this->config = $config;
|
|
$this->events = $events;
|
|
|
|
$this->plugins = array();
|
|
$this->paths = array();
|
|
$this->autoload = false;
|
|
|
|
if (!empty($config['plugins.paths'])) {
|
|
foreach ($config['plugins.paths'] as $dir => $prefix) {
|
|
$this->addPath($dir, $prefix);
|
|
}
|
|
}
|
|
|
|
$this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds a path to search for plugin class files. Paths are searched in
|
|
* the reverse order in which they are added.
|
|
*
|
|
* @param string $path Filesystem directory path
|
|
* @param string $prefix Optional class name prefix corresponding to the
|
|
* path
|
|
*
|
|
* @return Phergie_Plugin_Handler Provides a fluent interface
|
|
* @throws Phergie_Plugin_Exception
|
|
*/
|
|
public function addPath($path, $prefix = '')
|
|
{
|
|
if (!is_readable($path)) {
|
|
throw new Phergie_Plugin_Exception(
|
|
'Path "' . $path . '" does not reference a readable directory',
|
|
Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
|
|
);
|
|
}
|
|
|
|
$this->paths[] = array(
|
|
'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
|
|
'prefix' => $prefix
|
|
);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns metadata corresponding to a specified plugin.
|
|
*
|
|
* @param string $plugin Short name of the plugin class
|
|
*
|
|
* @throws Phergie_Plugin_Exception Class file can't be found
|
|
*
|
|
* @return array|boolean Associative array containing the path to the
|
|
* class file and its containing directory as well as the full
|
|
* class name
|
|
*/
|
|
public function getPluginInfo($plugin)
|
|
{
|
|
foreach (array_reverse($this->paths) as $path) {
|
|
$file = $path['path'] . $plugin . '.php';
|
|
if (file_exists($file)) {
|
|
$path = array(
|
|
'dir' => $path['path'],
|
|
'file' => $file,
|
|
'class' => $path['prefix'] . $plugin,
|
|
);
|
|
return $path;
|
|
}
|
|
}
|
|
|
|
// If the class can't be found, display an error
|
|
throw new Phergie_Plugin_Exception(
|
|
'Class file for plugin "' . $plugin . '" cannot be found',
|
|
Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds a plugin instance to the handler.
|
|
*
|
|
* @param string|Phergie_Plugin_Abstract $plugin Short name of the
|
|
* plugin class or a plugin object
|
|
* @param array $args Optional array of
|
|
* arguments to pass to the plugin constructor if a short name is
|
|
* passed for $plugin
|
|
*
|
|
* @return Phergie_Plugin_Abstract New plugin instance
|
|
*/
|
|
public function addPlugin($plugin, array $args = null)
|
|
{
|
|
// If a short plugin name is specified...
|
|
if (is_string($plugin)) {
|
|
$index = strtolower($plugin);
|
|
if (isset($this->plugins[$index])) {
|
|
return $this->plugins[$index];
|
|
}
|
|
|
|
// Attempt to locate and load the class
|
|
$info = $this->getPluginInfo($plugin);
|
|
$file = $info['file'];
|
|
$class = $info['class'];
|
|
include_once $file;
|
|
if (!class_exists($class, false)) {
|
|
throw new Phergie_Plugin_Exception(
|
|
'File "' . $file . '" does not contain class "' . $class . '"',
|
|
Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
|
|
);
|
|
}
|
|
|
|
// Check to ensure the class is a plugin class
|
|
if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
|
|
$msg
|
|
= 'Class for plugin "' . $plugin .
|
|
'" does not extend Phergie_Plugin_Abstract';
|
|
throw new Phergie_Plugin_Exception(
|
|
$msg,
|
|
Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
|
|
);
|
|
}
|
|
|
|
// Check to ensure the class can be instantiated
|
|
$reflection = new ReflectionClass($class);
|
|
if (!$reflection->isInstantiable()) {
|
|
throw new Phergie_Plugin_Exception(
|
|
'Class for plugin "' . $plugin . '" cannot be instantiated',
|
|
Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
|
|
);
|
|
}
|
|
|
|
// If the class is found, instantiate it
|
|
if (!empty($args)) {
|
|
$instance = $reflection->newInstanceArgs($args);
|
|
} else {
|
|
$instance = new $class;
|
|
}
|
|
|
|
// Store the instance
|
|
$this->plugins[$index] = $instance;
|
|
$plugin = $instance;
|
|
|
|
} elseif ($plugin instanceof Phergie_Plugin_Abstract) {
|
|
// If a plugin instance is specified...
|
|
|
|
// Add the plugin instance to the list of plugins
|
|
$this->plugins[strtolower($plugin->getName())] = $plugin;
|
|
}
|
|
|
|
// Configure and initialize the instance
|
|
$plugin->setPluginHandler($this);
|
|
$plugin->setConfig($this->config);
|
|
$plugin->setEventHandler($this->events);
|
|
$plugin->onLoad();
|
|
|
|
return $plugin;
|
|
}
|
|
|
|
/**
|
|
* Adds multiple plugin instances to the handler.
|
|
*
|
|
* @param array $plugins List of elements where each is of the form
|
|
* 'ShortPluginName' or array('ShortPluginName', array($arg1,
|
|
* ..., $argN))
|
|
*
|
|
* @return Phergie_Plugin_Handler Provides a fluent interface
|
|
*/
|
|
public function addPlugins(array $plugins)
|
|
{
|
|
foreach ($plugins as $plugin) {
|
|
if (is_array($plugin)) {
|
|
$this->addPlugin($plugin[0], $plugin[1]);
|
|
} else {
|
|
$this->addPlugin($plugin);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Removes a plugin instance from the handler.
|
|
*
|
|
* @param string|Phergie_Plugin_Abstract $plugin Short name of the
|
|
* plugin class or a plugin object
|
|
*
|
|
* @return Phergie_Plugin_Handler Provides a fluent interface
|
|
*/
|
|
public function removePlugin($plugin)
|
|
{
|
|
if ($plugin instanceof Phergie_Plugin_Abstract) {
|
|
$plugin = $plugin->getName();
|
|
}
|
|
$plugin = strtolower($plugin);
|
|
|
|
unset($this->plugins[$plugin]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns the corresponding instance for a specified plugin, loading it
|
|
* if it is not already loaded and autoloading is enabled.
|
|
*
|
|
* @param string $name Short name of the plugin class
|
|
*
|
|
* @return Phergie_Plugin_Abstract Plugin instance
|
|
*/
|
|
public function getPlugin($name)
|
|
{
|
|
// If the plugin is loaded, return the instance
|
|
$lower = strtolower($name);
|
|
if (isset($this->plugins[$lower])) {
|
|
return $this->plugins[$lower];
|
|
}
|
|
|
|
// If autoloading is disabled, display an error
|
|
if (!$this->autoload) {
|
|
$msg
|
|
= 'Plugin "' . $name . '" has been requested, ' .
|
|
'is not loaded, and autoload is disabled';
|
|
throw new Phergie_Plugin_Exception(
|
|
$msg,
|
|
Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
|
|
);
|
|
}
|
|
|
|
// If autoloading is enabled, attempt to load the plugin
|
|
$plugin = $this->addPlugin($name);
|
|
|
|
// Return the added plugin
|
|
return $plugin;
|
|
}
|
|
|
|
/**
|
|
* Returns the corresponding instances for multiple specified plugins,
|
|
* loading them if they are not already loaded and autoloading is
|
|
* enabled.
|
|
*
|
|
* @param array $names Optional list of short names of the plugin
|
|
* classes to which the returned plugin list will be limited,
|
|
* defaults to all presently loaded plugins
|
|
*
|
|
* @return array Associative array mapping lowercased plugin class short
|
|
* names to corresponding plugin instances
|
|
*/
|
|
public function getPlugins(array $names = array())
|
|
{
|
|
if (empty($names)) {
|
|
return $this->plugins;
|
|
}
|
|
|
|
$plugins = array();
|
|
foreach ($names as $name) {
|
|
$plugins[strtolower($name)] = $this->getPlugin($name);
|
|
}
|
|
return $plugins;
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not at least one instance of a specified plugin
|
|
* class is loaded.
|
|
*
|
|
* @param string $name Short name of the plugin class
|
|
*
|
|
* @return bool TRUE if an instance exists, FALSE otherwise
|
|
*/
|
|
public function hasPlugin($name)
|
|
{
|
|
return isset($this->plugins[strtolower($name)]);
|
|
}
|
|
|
|
/**
|
|
* Sets a flag used to determine whether plugins should be loaded
|
|
* automatically if they have not been explicitly loaded.
|
|
*
|
|
* @param bool $flag TRUE to have plugins autoload (default), FALSE
|
|
* otherwise
|
|
*
|
|
* @return Phergie_Plugin_Handler Provides a fluent interface.
|
|
*/
|
|
public function setAutoload($flag = true)
|
|
{
|
|
$this->autoload = $flag;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns the value of a flag used to determine whether plugins should
|
|
* be loaded automatically if they have not been explicitly loaded.
|
|
*
|
|
* @return bool TRUE if autoloading is enabled, FALSE otherwise
|
|
*/
|
|
public function getAutoload()
|
|
{
|
|
return $this->autoload;
|
|
}
|
|
|
|
/**
|
|
* Allows plugin instances to be accessed as properties of the handler.
|
|
*
|
|
* @param string $name Short name of the plugin
|
|
*
|
|
* @return Phergie_Plugin_Abstract Requested plugin instance
|
|
*/
|
|
public function __get($name)
|
|
{
|
|
return $this->getPlugin($name);
|
|
}
|
|
|
|
/**
|
|
* Allows plugin instances to be detected as properties of the handler.
|
|
*
|
|
* @param string $name Short name of the plugin
|
|
*
|
|
* @return bool TRUE if the plugin is loaded, FALSE otherwise
|
|
*/
|
|
public function __isset($name)
|
|
{
|
|
return $this->hasPlugin($name);
|
|
}
|
|
|
|
/**
|
|
* Allows plugin instances to be removed as properties of handler.
|
|
*
|
|
* @param string $name Short name of the plugin
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __unset($name)
|
|
{
|
|
$this->removePlugin($name);
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator for all currently loaded plugin instances.
|
|
*
|
|
* @return ArrayIterator
|
|
*/
|
|
public function getIterator()
|
|
{
|
|
return new $this->iteratorClass(
|
|
new ArrayIterator($this->plugins)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets the iterator class used for all currently loaded plugin
|
|
* instances.
|
|
*
|
|
* @param string $class Name of a class that extends FilterIterator
|
|
*
|
|
* @return Phergie_Plugin_Handler Provides a fluent API
|
|
* @throws Phergie_Plugin_Exception Class cannot be found or is not an
|
|
* FilterIterator-based class
|
|
*/
|
|
public function setIteratorClass($class)
|
|
{
|
|
$valid = true;
|
|
|
|
try {
|
|
$error_reporting = error_reporting(0); // ignore autoloader errors
|
|
$r = new ReflectionClass($class);
|
|
error_reporting($error_reporting);
|
|
if (!$r->isSubclassOf('FilterIterator')) {
|
|
$message = 'Class ' . $class . ' is not a subclass of FilterIterator';
|
|
$valid = false;
|
|
}
|
|
} catch (ReflectionException $e) {
|
|
$message = $e->getMessage();
|
|
$valid = false;
|
|
}
|
|
|
|
if (!$valid) {
|
|
throw new Phergie_Plugin_Exception(
|
|
$message,
|
|
Phergie_Plugin_Exception::ERR_INVALID_ITERATOR_CLASS
|
|
);
|
|
}
|
|
|
|
$this->iteratorClass = $class;
|
|
}
|
|
|
|
/**
|
|
* Proxies method calls to all plugins containing the called method.
|
|
*
|
|
* @param string $name Name of the method called
|
|
* @param array $args Arguments passed in the method call
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __call($name, array $args)
|
|
{
|
|
foreach ($this->getIterator() as $plugin) {
|
|
call_user_func_array(array($plugin, $name), $args);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of plugins contained within the handler.
|
|
*
|
|
* @return int Plugin count
|
|
*/
|
|
public function count()
|
|
{
|
|
return count($this->plugins);
|
|
}
|
|
}
|