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 * Constructor to initialize class properties and add the path for core
75 * @param Phergie_Config $config configuration to pass to any
77 * @param Phergie_Event_Handler $events event handler to pass to any
82 public function __construct(
83 Phergie_Config $config,
84 Phergie_Event_Handler $events
86 $this->config = $config;
87 $this->events = $events;
89 $this->plugins = array();
90 $this->paths = array();
91 $this->autoload = false;
93 $this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
98 * Adds a path to search for plugin class files. Paths are searched in
99 * the reverse order in which they are added.
101 * @param string $path Filesystem directory path
102 * @param string $prefix Optional class name prefix corresponding to the
105 * @return Phergie_Plugin_Handler Provides a fluent interface
106 * @throws Phergie_Plugin_Exception
108 public function addPath($path, $prefix = '')
110 if (!is_readable($path)) {
111 throw new Phergie_Plugin_Exception(
112 'Path "' . $path . '" does not reference a readable directory',
113 Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
117 $this->paths[] = array(
118 'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
126 * Returns metadata corresponding to a specified plugin.
128 * @param string $plugin Short name of the plugin class
129 * @throws Phergie_Plugin_Exception Class file can't be found
131 * @return array|boolean Associative array containing the path to the
132 * class file and its containing directory as well as the full
135 public function getPluginInfo($plugin)
137 foreach (array_reverse($this->paths) as $path) {
138 $file = $path['path'] . $plugin . '.php';
139 if (file_exists($file)) {
141 'dir' => $path['path'],
143 'class' => $path['prefix'] . $plugin,
149 // If the class can't be found, display an error
150 throw new Phergie_Plugin_Exception(
151 'Class file for plugin "' . $plugin . '" cannot be found',
152 Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
157 * Adds a plugin instance to the handler.
159 * @param string|Phergie_Plugin_Abstract $plugin Short name of the
160 * plugin class or a plugin object
161 * @param array $args Optional array of
162 * arguments to pass to the plugin constructor if a short name is
165 * @return Phergie_Plugin_Abstract New plugin instance
167 public function addPlugin($plugin, array $args = null)
169 // If a short plugin name is specified...
170 if (is_string($plugin)) {
171 $index = strtolower($plugin);
172 if (isset($this->plugins[$index])) {
173 return $this->plugins[$index];
176 // Attempt to locate and load the class
177 $info = $this->getPluginInfo($plugin);
178 $file = $info['file'];
179 $class = $info['class'];
181 if (!class_exists($class, false)) {
182 throw new Phergie_Plugin_Exception(
183 'File "' . $file . '" does not contain class "' . $class . '"',
184 Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
188 // Check to ensure the class is a plugin class
189 if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
191 = 'Class for plugin "' . $plugin .
192 '" does not extend Phergie_Plugin_Abstract';
193 throw new Phergie_Plugin_Exception(
195 Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
199 // Check to ensure the class can be instantiated
200 $reflection = new ReflectionClass($class);
201 if (!$reflection->isInstantiable()) {
202 throw new Phergie_Plugin_Exception(
203 'Class for plugin "' . $plugin . '" cannot be instantiated',
204 Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
208 // If the class is found, instantiate it
210 $instance = $reflection->newInstanceArgs($args);
212 $instance = new $class;
215 // Store the instance
216 $this->plugins[$index] = $instance;
219 } elseif ($plugin instanceof Phergie_Plugin_Abstract) {
220 // If a plugin instance is specified...
222 // Add the plugin instance to the list of plugins
223 $this->plugins[strtolower($plugin->getName())] = $plugin;
226 // Configure and initialize the instance
227 $plugin->setPluginHandler($this);
228 $plugin->setConfig($this->config);
229 $plugin->setEventHandler($this->events);
236 * Adds multiple plugin instances to the handler.
238 * @param array $plugins List of elements where each is of the form
239 * 'ShortPluginName' or array('ShortPluginName', array($arg1,
242 * @return Phergie_Plugin_Handler Provides a fluent interface
244 public function addPlugins(array $plugins)
246 foreach ($plugins as $plugin) {
247 if (is_array($plugin)) {
248 $this->addPlugin($plugin[0], $plugin[1]);
250 $this->addPlugin($plugin);
258 * Removes a plugin instance from the handler.
260 * @param string|Phergie_Plugin_Abstract $plugin Short name of the
261 * plugin class or a plugin object
263 * @return Phergie_Plugin_Handler Provides a fluent interface
265 public function removePlugin($plugin)
267 if ($plugin instanceof Phergie_Plugin_Abstract) {
268 $plugin = $plugin->getName();
270 $plugin = strtolower($plugin);
272 unset($this->plugins[$plugin]);
278 * Returns the corresponding instance for a specified plugin, loading it
279 * if it is not already loaded and autoloading is enabled.
281 * @param string $name Short name of the plugin class
283 * @return Phergie_Plugin_Abstract Plugin instance
285 public function getPlugin($name)
287 // If the plugin is loaded, return the instance
288 $lower = strtolower($name);
289 if (isset($this->plugins[$lower])) {
290 return $this->plugins[$lower];
293 // If autoloading is disabled, display an error
294 if (!$this->autoload) {
296 = 'Plugin "' . $name . '" has been requested, ' .
297 'is not loaded, and autoload is disabled';
298 throw new Phergie_Plugin_Exception(
300 Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
304 // If autoloading is enabled, attempt to load the plugin
305 $plugin = $this->addPlugin($name);
307 // Return the added plugin
312 * Returns the corresponding instances for multiple specified plugins,
313 * loading them if they are not already loaded and autoloading is
316 * @param array $names Optional list of short names of the plugin
317 * classes to which the returned plugin list will be limited,
318 * defaults to all presently loaded plugins
320 * @return array Associative array mapping lowercased plugin class short
321 * names to corresponding plugin instances
323 public function getPlugins(array $names = array())
326 return $this->plugins;
330 foreach ($names as $name) {
331 $plugins[$name] = $this->getPlugin($name);
337 * Returns whether or not at least one instance of a specified plugin
340 * @param string $name Short name of the plugin class
342 * @return bool TRUE if an instance exists, FALSE otherwise
344 public function hasPlugin($name)
346 return isset($this->plugins[strtolower($name)]);
350 * Sets a flag used to determine whether plugins should be loaded
351 * automatically if they have not been explicitly loaded.
353 * @param bool $flag TRUE to have plugins autoload (default), FALSE
356 * @return Phergie_Plugin_Handler Provides a fluent interface.
358 public function setAutoload($flag = true)
360 $this->autoload = $flag;
366 * Returns the value of a flag used to determine whether plugins should
367 * be loaded automatically if they have not been explicitly loaded.
369 * @return bool TRUE if autoloading is enabled, FALSE otherwise
371 public function getAutoload()
373 return $this->autoload;
377 * Allows plugin instances to be accessed as properties of the handler.
379 * @param string $name Short name of the plugin
381 * @return Phergie_Plugin_Abstract Requested plugin instance
383 public function __get($name)
385 return $this->getPlugin($name);
389 * Allows plugin instances to be detected as properties of the handler.
391 * @param string $name Short name of the plugin
393 * @return bool TRUE if the plugin is loaded, FALSE otherwise
395 public function __isset($name)
397 return $this->hasPlugin($name);
401 * Allows plugin instances to be removed as properties of handler.
403 * @param string $name Short name of the plugin
407 public function __unset($name)
409 $this->removePlugin($name);
413 * Returns an iterator for all currently loaded plugin instances.
415 * @return ArrayIterator
417 public function getIterator()
419 return new ArrayIterator($this->plugins);
423 * Proxies method calls to all plugins containing the called method. An
424 * individual plugin may short-circuit this process by explicitly
427 * @param string $name Name of the method called
428 * @param array $args Arguments passed in the method call
430 * @return bool FALSE if a plugin short-circuits processing by returning
431 * FALSE, TRUE otherwise
433 public function __call($name, array $args)
435 foreach ($this->plugins as $plugin) {
436 if (call_user_func_array(array($plugin, $name), $args) === false) {
444 * Returns the number of plugins contained within the handler.
446 * @return int Plugin count
448 public function count()
450 return count($this->plugins);