9 * This source file is subject to the new BSD license that is bundled
10 * with this package in the file LICENSE.
11 * It is also available through the world-wide-web at this URL:
12 * http://phergie.org/license
16 * @author Phergie Development Team <team@phergie.org>
17 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
18 * @license http://phergie.org/license New BSD License
19 * @link http://pear.phergie.org/package/Phergie
23 * Handles on-demand loading of, iteration over, and access to plugins.
27 * @author Phergie Development Team <team@phergie.org>
28 * @license http://phergie.org/license New BSD License
29 * @link http://pear.phergie.org/package/Phergie
31 class Phergie_Plugin_Handler implements IteratorAggregate, Countable
34 * Current list of plugin instances
41 * Paths in which to search for plugin class files
48 * Flag indicating whether plugin classes should be instantiated on
49 * demand if they are requested but no instance currently exists
56 * Phergie_Config instance that should be passed in to any plugin
57 * instantiated within the handler
64 * Phergie_Event_Handler instance that should be passed in to any plugin
65 * instantiated within the handler
67 * @var Phergie_Event_Handler
72 * Name of the class to use for iterating over all currently loaded
77 protected $iteratorClass = 'Phergie_Plugin_Iterator';
80 * Constructor to initialize class properties and add the path for core
83 * @param Phergie_Config $config configuration to pass to any
85 * @param Phergie_Event_Handler $events event handler to pass to any
90 public function __construct(
91 Phergie_Config $config,
92 Phergie_Event_Handler $events
94 $this->config = $config;
95 $this->events = $events;
97 $this->plugins = array();
98 $this->paths = array();
99 $this->autoload = false;
101 if (!empty($config['plugins.paths'])) {
102 foreach ($config['plugins.paths'] as $dir => $prefix) {
103 $this->addPath($dir, $prefix);
107 $this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
112 * Adds a path to search for plugin class files. Paths are searched in
113 * the reverse order in which they are added.
115 * @param string $path Filesystem directory path
116 * @param string $prefix Optional class name prefix corresponding to the
119 * @return Phergie_Plugin_Handler Provides a fluent interface
120 * @throws Phergie_Plugin_Exception
122 public function addPath($path, $prefix = '')
124 if (!is_readable($path)) {
125 throw new Phergie_Plugin_Exception(
126 'Path "' . $path . '" does not reference a readable directory',
127 Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
131 $this->paths[] = array(
132 'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
140 * Returns metadata corresponding to a specified plugin.
142 * @param string $plugin Short name of the plugin class
144 * @throws Phergie_Plugin_Exception Class file can't be found
146 * @return array|boolean Associative array containing the path to the
147 * class file and its containing directory as well as the full
150 public function getPluginInfo($plugin)
152 foreach (array_reverse($this->paths) as $path) {
153 $file = $path['path'] . $plugin . '.php';
154 if (file_exists($file)) {
156 'dir' => $path['path'],
158 'class' => $path['prefix'] . $plugin,
164 // If the class can't be found, display an error
165 throw new Phergie_Plugin_Exception(
166 'Class file for plugin "' . $plugin . '" cannot be found',
167 Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
172 * Adds a plugin instance to the handler.
174 * @param string|Phergie_Plugin_Abstract $plugin Short name of the
175 * plugin class or a plugin object
176 * @param array $args Optional array of
177 * arguments to pass to the plugin constructor if a short name is
180 * @return Phergie_Plugin_Abstract New plugin instance
182 public function addPlugin($plugin, array $args = null)
184 // If a short plugin name is specified...
185 if (is_string($plugin)) {
186 $index = strtolower($plugin);
187 if (isset($this->plugins[$index])) {
188 return $this->plugins[$index];
191 // Attempt to locate and load the class
192 $info = $this->getPluginInfo($plugin);
193 $file = $info['file'];
194 $class = $info['class'];
196 if (!class_exists($class, false)) {
197 throw new Phergie_Plugin_Exception(
198 'File "' . $file . '" does not contain class "' . $class . '"',
199 Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
203 // Check to ensure the class is a plugin class
204 if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
206 = 'Class for plugin "' . $plugin .
207 '" does not extend Phergie_Plugin_Abstract';
208 throw new Phergie_Plugin_Exception(
210 Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
214 // Check to ensure the class can be instantiated
215 $reflection = new ReflectionClass($class);
216 if (!$reflection->isInstantiable()) {
217 throw new Phergie_Plugin_Exception(
218 'Class for plugin "' . $plugin . '" cannot be instantiated',
219 Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
223 // If the class is found, instantiate it
225 $instance = $reflection->newInstanceArgs($args);
227 $instance = new $class;
230 // Store the instance
231 $this->plugins[$index] = $instance;
234 } elseif ($plugin instanceof Phergie_Plugin_Abstract) {
235 // If a plugin instance is specified...
237 // Add the plugin instance to the list of plugins
238 $this->plugins[strtolower($plugin->getName())] = $plugin;
241 // Configure and initialize the instance
242 $plugin->setPluginHandler($this);
243 $plugin->setConfig($this->config);
244 $plugin->setEventHandler($this->events);
251 * Adds multiple plugin instances to the handler.
253 * @param array $plugins List of elements where each is of the form
254 * 'ShortPluginName' or array('ShortPluginName', array($arg1,
257 * @return Phergie_Plugin_Handler Provides a fluent interface
259 public function addPlugins(array $plugins)
261 foreach ($plugins as $plugin) {
262 if (is_array($plugin)) {
263 $this->addPlugin($plugin[0], $plugin[1]);
265 $this->addPlugin($plugin);
273 * Removes a plugin instance from the handler.
275 * @param string|Phergie_Plugin_Abstract $plugin Short name of the
276 * plugin class or a plugin object
278 * @return Phergie_Plugin_Handler Provides a fluent interface
280 public function removePlugin($plugin)
282 if ($plugin instanceof Phergie_Plugin_Abstract) {
283 $plugin = $plugin->getName();
285 $plugin = strtolower($plugin);
287 unset($this->plugins[$plugin]);
293 * Returns the corresponding instance for a specified plugin, loading it
294 * if it is not already loaded and autoloading is enabled.
296 * @param string $name Short name of the plugin class
298 * @return Phergie_Plugin_Abstract Plugin instance
300 public function getPlugin($name)
302 // If the plugin is loaded, return the instance
303 $lower = strtolower($name);
304 if (isset($this->plugins[$lower])) {
305 return $this->plugins[$lower];
308 // If autoloading is disabled, display an error
309 if (!$this->autoload) {
311 = 'Plugin "' . $name . '" has been requested, ' .
312 'is not loaded, and autoload is disabled';
313 throw new Phergie_Plugin_Exception(
315 Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
319 // If autoloading is enabled, attempt to load the plugin
320 $plugin = $this->addPlugin($name);
322 // Return the added plugin
327 * Returns the corresponding instances for multiple specified plugins,
328 * loading them if they are not already loaded and autoloading is
331 * @param array $names Optional list of short names of the plugin
332 * classes to which the returned plugin list will be limited,
333 * defaults to all presently loaded plugins
335 * @return array Associative array mapping lowercased plugin class short
336 * names to corresponding plugin instances
338 public function getPlugins(array $names = array())
341 return $this->plugins;
345 foreach ($names as $name) {
346 $plugins[strtolower($name)] = $this->getPlugin($name);
352 * Returns whether or not at least one instance of a specified plugin
355 * @param string $name Short name of the plugin class
357 * @return bool TRUE if an instance exists, FALSE otherwise
359 public function hasPlugin($name)
361 return isset($this->plugins[strtolower($name)]);
365 * Sets a flag used to determine whether plugins should be loaded
366 * automatically if they have not been explicitly loaded.
368 * @param bool $flag TRUE to have plugins autoload (default), FALSE
371 * @return Phergie_Plugin_Handler Provides a fluent interface.
373 public function setAutoload($flag = true)
375 $this->autoload = $flag;
381 * Returns the value of a flag used to determine whether plugins should
382 * be loaded automatically if they have not been explicitly loaded.
384 * @return bool TRUE if autoloading is enabled, FALSE otherwise
386 public function getAutoload()
388 return $this->autoload;
392 * Allows plugin instances to be accessed as properties of the handler.
394 * @param string $name Short name of the plugin
396 * @return Phergie_Plugin_Abstract Requested plugin instance
398 public function __get($name)
400 return $this->getPlugin($name);
404 * Allows plugin instances to be detected as properties of the handler.
406 * @param string $name Short name of the plugin
408 * @return bool TRUE if the plugin is loaded, FALSE otherwise
410 public function __isset($name)
412 return $this->hasPlugin($name);
416 * Allows plugin instances to be removed as properties of handler.
418 * @param string $name Short name of the plugin
422 public function __unset($name)
424 $this->removePlugin($name);
428 * Returns an iterator for all currently loaded plugin instances.
430 * @return ArrayIterator
432 public function getIterator()
434 return new $this->iteratorClass(
435 new ArrayIterator($this->plugins)
440 * Sets the iterator class used for all currently loaded plugin
443 * @param string $class Name of a class that extends FilterIterator
445 * @return Phergie_Plugin_Handler Provides a fluent API
446 * @throws Phergie_Plugin_Exception Class cannot be found or is not an
447 * FilterIterator-based class
449 public function setIteratorClass($class)
454 $error_reporting = error_reporting(0); // ignore autoloader errors
455 $r = new ReflectionClass($class);
456 error_reporting($error_reporting);
457 if (!$r->isSubclassOf('FilterIterator')) {
458 $message = 'Class ' . $class . ' is not a subclass of FilterIterator';
461 } catch (ReflectionException $e) {
462 $message = $e->getMessage();
467 throw new Phergie_Plugin_Exception(
469 Phergie_Plugin_Exception::ERR_INVALID_ITERATOR_CLASS
473 $this->iteratorClass = $class;
477 * Proxies method calls to all plugins containing the called method.
479 * @param string $name Name of the method called
480 * @param array $args Arguments passed in the method call
484 public function __call($name, array $args)
486 foreach ($this->getIterator() as $plugin) {
487 call_user_func_array(array($plugin, $name), $args);
493 * Returns the number of plugins contained within the handler.
495 * @return int Plugin count
497 public function count()
499 return count($this->plugins);