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 * Iterator used for selectively proxying method calls to contained
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 $this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
106 * Adds a path to search for plugin class files. Paths are searched in
107 * the reverse order in which they are added.
109 * @param string $path Filesystem directory path
110 * @param string $prefix Optional class name prefix corresponding to the
113 * @return Phergie_Plugin_Handler Provides a fluent interface
114 * @throws Phergie_Plugin_Exception
116 public function addPath($path, $prefix = '')
118 if (!is_readable($path)) {
119 throw new Phergie_Plugin_Exception(
120 'Path "' . $path . '" does not reference a readable directory',
121 Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
125 $this->paths[] = array(
126 'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
134 * Returns metadata corresponding to a specified plugin.
136 * @param string $plugin Short name of the plugin class
137 * @throws Phergie_Plugin_Exception Class file can't be found
139 * @return array|boolean Associative array containing the path to the
140 * class file and its containing directory as well as the full
143 public function getPluginInfo($plugin)
145 foreach (array_reverse($this->paths) as $path) {
146 $file = $path['path'] . $plugin . '.php';
147 if (file_exists($file)) {
149 'dir' => $path['path'],
151 'class' => $path['prefix'] . $plugin,
157 // If the class can't be found, display an error
158 throw new Phergie_Plugin_Exception(
159 'Class file for plugin "' . $plugin . '" cannot be found',
160 Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
165 * Adds a plugin instance to the handler.
167 * @param string|Phergie_Plugin_Abstract $plugin Short name of the
168 * plugin class or a plugin object
169 * @param array $args Optional array of
170 * arguments to pass to the plugin constructor if a short name is
173 * @return Phergie_Plugin_Abstract New plugin instance
175 public function addPlugin($plugin, array $args = null)
177 // If a short plugin name is specified...
178 if (is_string($plugin)) {
179 $index = strtolower($plugin);
180 if (isset($this->plugins[$index])) {
181 return $this->plugins[$index];
184 // Attempt to locate and load the class
185 $info = $this->getPluginInfo($plugin);
186 $file = $info['file'];
187 $class = $info['class'];
189 if (!class_exists($class, false)) {
190 throw new Phergie_Plugin_Exception(
191 'File "' . $file . '" does not contain class "' . $class . '"',
192 Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
196 // Check to ensure the class is a plugin class
197 if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
199 = 'Class for plugin "' . $plugin .
200 '" does not extend Phergie_Plugin_Abstract';
201 throw new Phergie_Plugin_Exception(
203 Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
207 // Check to ensure the class can be instantiated
208 $reflection = new ReflectionClass($class);
209 if (!$reflection->isInstantiable()) {
210 throw new Phergie_Plugin_Exception(
211 'Class for plugin "' . $plugin . '" cannot be instantiated',
212 Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
216 // If the class is found, instantiate it
218 $instance = $reflection->newInstanceArgs($args);
220 $instance = new $class;
223 // Store the instance
224 $this->plugins[$index] = $instance;
227 } elseif ($plugin instanceof Phergie_Plugin_Abstract) {
228 // If a plugin instance is specified...
230 // Add the plugin instance to the list of plugins
231 $this->plugins[strtolower($plugin->getName())] = $plugin;
234 // Configure and initialize the instance
235 $plugin->setPluginHandler($this);
236 $plugin->setConfig($this->config);
237 $plugin->setEventHandler($this->events);
244 * Adds multiple plugin instances to the handler.
246 * @param array $plugins List of elements where each is of the form
247 * 'ShortPluginName' or array('ShortPluginName', array($arg1,
250 * @return Phergie_Plugin_Handler Provides a fluent interface
252 public function addPlugins(array $plugins)
254 foreach ($plugins as $plugin) {
255 if (is_array($plugin)) {
256 $this->addPlugin($plugin[0], $plugin[1]);
258 $this->addPlugin($plugin);
266 * Removes a plugin instance from the handler.
268 * @param string|Phergie_Plugin_Abstract $plugin Short name of the
269 * plugin class or a plugin object
271 * @return Phergie_Plugin_Handler Provides a fluent interface
273 public function removePlugin($plugin)
275 if ($plugin instanceof Phergie_Plugin_Abstract) {
276 $plugin = $plugin->getName();
278 $plugin = strtolower($plugin);
280 unset($this->plugins[$plugin]);
286 * Returns the corresponding instance for a specified plugin, loading it
287 * if it is not already loaded and autoloading is enabled.
289 * @param string $name Short name of the plugin class
291 * @return Phergie_Plugin_Abstract Plugin instance
293 public function getPlugin($name)
295 // If the plugin is loaded, return the instance
296 $lower = strtolower($name);
297 if (isset($this->plugins[$lower])) {
298 return $this->plugins[$lower];
301 // If autoloading is disabled, display an error
302 if (!$this->autoload) {
304 = 'Plugin "' . $name . '" has been requested, ' .
305 'is not loaded, and autoload is disabled';
306 throw new Phergie_Plugin_Exception(
308 Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
312 // If autoloading is enabled, attempt to load the plugin
313 $plugin = $this->addPlugin($name);
315 // Return the added plugin
320 * Returns the corresponding instances for multiple specified plugins,
321 * loading them if they are not already loaded and autoloading is
324 * @param array $names Optional list of short names of the plugin
325 * classes to which the returned plugin list will be limited,
326 * defaults to all presently loaded plugins
328 * @return array Associative array mapping lowercased plugin class short
329 * names to corresponding plugin instances
331 public function getPlugins(array $names = array())
334 return $this->plugins;
338 foreach ($names as $name) {
339 $plugins[strtolower($name)] = $this->getPlugin($name);
345 * Returns whether or not at least one instance of a specified plugin
348 * @param string $name Short name of the plugin class
350 * @return bool TRUE if an instance exists, FALSE otherwise
352 public function hasPlugin($name)
354 return isset($this->plugins[strtolower($name)]);
358 * Sets a flag used to determine whether plugins should be loaded
359 * automatically if they have not been explicitly loaded.
361 * @param bool $flag TRUE to have plugins autoload (default), FALSE
364 * @return Phergie_Plugin_Handler Provides a fluent interface.
366 public function setAutoload($flag = true)
368 $this->autoload = $flag;
374 * Returns the value of a flag used to determine whether plugins should
375 * be loaded automatically if they have not been explicitly loaded.
377 * @return bool TRUE if autoloading is enabled, FALSE otherwise
379 public function getAutoload()
381 return $this->autoload;
385 * Allows plugin instances to be accessed as properties of the handler.
387 * @param string $name Short name of the plugin
389 * @return Phergie_Plugin_Abstract Requested plugin instance
391 public function __get($name)
393 return $this->getPlugin($name);
397 * Allows plugin instances to be detected as properties of the handler.
399 * @param string $name Short name of the plugin
401 * @return bool TRUE if the plugin is loaded, FALSE otherwise
403 public function __isset($name)
405 return $this->hasPlugin($name);
409 * Allows plugin instances to be removed as properties of handler.
411 * @param string $name Short name of the plugin
415 public function __unset($name)
417 $this->removePlugin($name);
421 * Returns an iterator for all currently loaded plugin instances.
423 * @return ArrayIterator
425 public function getIterator()
427 if (empty($this->iterator)) {
428 $this->iterator = new Phergie_Plugin_Iterator(
429 new ArrayIterator($this->plugins)
432 return $this->iterator;
436 * Sets the iterator for all currently loaded plugin instances.
438 * @param Iterator $iterator Plugin iterator
440 * @return Phergie_Plugin_Handler Provides a fluent interface
442 public function setIterator(Iterator $iterator)
444 $this->iterator = $iterator;
449 * Proxies method calls to all plugins containing the called method.
451 * @param string $name Name of the method called
452 * @param array $args Arguments passed in the method call
456 public function __call($name, array $args)
458 foreach ($this->getIterator() as $plugin) {
459 call_user_func_array(array($plugin, $name), $args);
465 * Returns the number of plugins contained within the handler.
467 * @return int Plugin count
469 public function count()
471 return count($this->plugins);