A number of Zend Framework components are pluggable, and allow loading
of dynamic functionality by specifying a class prefix and path to class
files that are not necessarily on the include_path or do
not necessarily follow traditional naming conventions.
Zend_Loader_PluginLoader
provides common functionality for
this process.
The basic usage of the PluginLoader
follows Zend Framework
naming conventions of one class per file, using the underscore as a
directory separator when resolving paths. It allows passing an optional
class prefix to prepend when determining if a particular plugin class is
loaded. Additionally, paths are searched in LIFO order. Due to the LIFO
search and the class prefixes, this allows you to define namespaces for your
plugins, and thus override plugins from paths registered earlier.
First, let's assume the following directory structure and class files, and that the top level directory and library directory are on the include_path:
application/ modules/ foo/ views/ helpers/ FormLabel.php FormSubmit.php bar/ views/ helpers/ FormSubmit.php library/ Zend/ View/ Helper/ FormLabel.php FormSubmit.php FormText.php
Now, let's create a plugin loader to address the various view helper repositories available:
$loader = new Zend_Loader_PluginLoader(); $loader->addPrefixPath('Zend_View_Helper', 'Zend/View/Helper/') ->addPrefixPath('Foo_View_Helper', 'application/modules/foo/views/helpers') ->addPrefixPath('Bar_View_Helper', 'application/modules/bar/views/helpers');
We can then load a given view helper using just the portion of the class name following the prefixes as defined when adding the paths:
// load 'FormText' helper: $formTextClass = $loader->load('FormText'); // 'Zend_View_Helper_FormText'; // load 'FormLabel' helper: $formLabelClass = $loader->load('FormLabel'); // 'Foo_View_Helper_FormLabel' // load 'FormSubmit' helper: $formSubmitClass = $loader->load('FormSubmit'); // 'Bar_View_Helper_FormSubmit'
Once the class is loaded, we can now instantiate it.
Note
In some cases, you may use the same prefix for multiple paths.
Zend_Loader_PluginLoader
actually registers an
array of paths for each given prefix; the last one registered
will be the first one checked. This is particularly useful if
you are utilizing incubator components.
Paths may be defined at instantiation
You may optionally provide an array of prefix / path pairs (or prefix / paths -- plural paths are allowed) as a parameter to the constructor:
$loader = new Zend_Loader_PluginLoader(array( 'Zend_View_Helper' => 'Zend/View/Helper/', 'Foo_View_Helper' => 'application/modules/foo/views/helpers', 'Bar_View_Helper' => 'application/modules/bar/views/helpers' ));
Zend_Loader_PluginLoader
also optionally allows you to
share plugins across plugin-aware objects, without needing to
utilize a singleton instance. It does so via a static registry.
Indicate the registry name at instantiation as the second parameter
to the constructor:
// Store plugins in static registry 'foobar': $loader = new Zend_Loader_PluginLoader(array(), 'foobar');
Other components that instantiate the PluginLoader
using the same registry name will then have access to already loaded
paths and plugins.
The example in the previous section shows how to add paths to a plugin loader. What if you want to determine the paths already loaded, or remove one or more?
-
getPaths($prefix = null)
returns all paths as prefix / path pairs if no$prefix
is provided, or just the paths registered for a given prefix if a$prefix
is present. -
clearPaths($prefix = null)
will clear all registered paths by default, or only those associated with a given prefix, if the$prefix
is provided and present in the stack. -
removePrefixPath($prefix, $path = null)
allows you to selectively remove a specific path associated with a given prefix. If no$path
is provided, all paths for that prefix are removed. If a$path
is provided and exists for that prefix, only that path will be removed.
Sometimes you simply want to determine if a plugin class has been
loaded before you perform an action. isLoaded()
takes a
plugin name, and returns the status.
Another common use case for the PluginLoader
is to
determine fully qualified plugin class names of loaded classes;
getClassName()
provides this functionality. Typically,
this would be used in conjunction with isLoaded()
:
if ($loader->isLoaded('Adapter')) { $class = $loader->getClassName('Adapter'); $adapter = call_user_func(array($class, 'getInstance')); }
Plugin loading can be an expensive operation. At its heart, it needs to loop through each prefix, then each path on the prefix, until it finds a file that matches -- and which defines the class expected. In cases where the file exists but does not define the class, an error will be added to the PHP error stack, which is also an expensive operation. The question then turns to: how can you keep the flexibility of plugins and also address performance?
Zend_Loader_PluginLoader
offers an opt-in feature for
just this situation, a class file include cache. When enabled, it
will create a file that contains all successful includes which you
can then call from your bootstrap. Using this strategy, you can
greatly improve the performance of your production servers.
Example 515. Using the PluginLoader class file include cache
To use the class file include cache, simply drop the following code into your bootstrap:
$classFileIncCache = APPLICATION_PATH . '/../data/pluginLoaderCache.php'; if (file_exists($classFileIncCache)) { include_once $classFileIncCache; } Zend_Loader_PluginLoader::setIncludeFileCache($classFileIncCache);
Obviously, the path and filename will vary based on your needs. This code should come as early as possible, to ensure that plugin-based components can make use of it.
During development, you may wish to disable the cache. One method for doing so is to use a configuration key for determining whether or not the plugin loader should cache.
$classFileIncCache = APPLICATION_PATH . '/../data/pluginLoaderCache.php'; if (file_exists($classFileIncCache)) { include_once $classFileIncCache; } if ($config->enablePluginLoaderCache) { Zend_Loader_PluginLoader::setIncludeFileCache($classFileIncCache); }
This technique allows you to keep your modifications to your configuration file rather than code.