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);
 | 
						|
    }
 | 
						|
}
 |