Many of the ideas and possibilities below were synthesized from discussions with the community, Andi, Darby, and Stas. However, much of the typing is mine, so flaws, typos, etc. can be credited to me
- Simplicity: simplify use of a plugin by encapsulating construction and management details exposed in the plugin API.
- Automation: auto-find/load the plugin source file, and auto-instantiate.
- Exceptions: throw exceptions in the factory on failure to load or instantiate the plugin.
- Bookkeeping: if we need to enforce singleton, or centrally manage instances, a factory provides a single point at which to collect processes associated with recording information about (and using) created instances.
- Set the Zend Framework standard and best practice for coding factories.
- Support code analysis tools (e.g. parsing code to determine which methods are called where and by which classes, etc.)
- Support direct instantiation of plugins via new classname(), when a factory pattern is not needed or desired by the developer
- Support code completion by IDEs while developers are writing code, to help avoid spelling mistakes and reduce effort required to find and use classes, methods, and configuration options
- Switch: reduce the code required for mapping input choices to plugins (see "Convenience Factories" below).
Directly instantiating classes wherever possible, practical, and reasonable improves the benefits of using tools like IDEs.
Currently, the ZF has preferred using strings and mapping the strings to concrete classes within the factory -e.g. $adapter = new Zend_Db('PDO_MYSQL', $optionsArray);
No one proposed a solution to the dilemma posed by using constants in conjunction with developer created subclasses. So, in this case, a Zend Framework's key objective takes precedence: consistency
Thus, we expect to continue using strings as parameters to factory methods, like Zend_Db::factory($adapterString). The adapter strings should contain all uppercase letters. However, the factory method should ignore the case of the strings. The strings should follow a simple convention of joining compound words (if needed), using underscore characters ('_').
If the strings correlate to classes, then the strings should be unique enough to enable identifying and constructing the class name, by making the rightmost "word" in the string correspond directly to the class name of the instance object resulting from the factory method. Factory methods should follow a convention of checking a well-known location for user-supplied adapter/plugin classes, if the supplied string does not match a class included with the Zend Framework.
Factories ought to know by convention where to find the adapters, including a userland "plugin" directory, where developers can drop in their own, custom adapters.
If the factory method is more of a convenience method that simplifies the process of constructing and/or initializing the plugin, then the factory can simply return an instance of the plugin.
If the factory method only encapsulates a "switch" contruct, then the factory can simply return an instance of the plugin.
If userland code dynamically chooses $pluginType based on input, then there must still be some sort of mapping between input values and values for $pluginType, so embedding the switch() in the factory does not, by itself, result in significantly simpler code, and might actually result in duplication of the exception/error checking.
These factories provide additional methods beyond those of convenience factories, and return an instance of the factory, instead of an instance of the plugin, although an instance of the plugin remains a private member of the object returned by the factory. This form of run-time subclassing has been named dependency injection by Martin Fowler.
A convenience factory might be used to create the plugin instance stored in the object returned by the composition factory.
Composition factories are equivalent to creating a common superclass from which plugins can inherit, but lack the "switch construct" and bookkeeping. Additionally, multiple or mixin style inheritance are needed if the plugins are not all related by a common superclass.
|For simpler cases, using single inheritance with a common superclass often yields simpler code than using a composition factory.|
|This section needs some improvement.|
The framework must strive for consistent application in balancing between using factory methods, factory objects, and direct instantiation. Additionally, use of adapters/plugins should follow best practices and adhere to consistent patterns of use throughout the framework.
We prefer supporting code completion, when practical.
Simplicity is our mantra, so the default preference of the Zend Framework should take the form of:
$instance = Zend_Class_Subclass($config_array); # direct
$instance = Zend_Class_Factory('Subclass', $config_array); # needed for dependency injection
using func_get_args() allows us to do:
However, this approach to factory methods does not address one of the key stated goals above. Specifically, it fails to provide one of the benefits of convenience factories by eliminating the need for "switch" like constructs in userland. So if the developer must call a factory method with 3 arguments in one situation, but 4 arguments in another, instead of just loading up a $options PHP array with <keyword,value> pairs, the developer will need something like the switch statement described in the "convenience factories" section.
Some disadvantages of eval:
- Eval cannot benefit from bytecode caches and optimizations
- Eval is very hard to debug
|This section needs some improvement.|
|Guidelines are not as stringent or imperative as "best practices", and depend more intimately with the specifics of each situation.|
When choosing to use a factory method, the factory method should do something that either does not belong in the constructor of the instance created by the factory, or the factory method something that can not be done in that constructor.
Sometimes we have Zend_Class_Subclass1 and Zend_Class_Subclass2, and the code performing construction of both classes follow similar logic. Factories factor construction logic into the factory class. Inheritance also can help factor such construction logic into a common superclass, if all plugins can logically share a common superclass.
The facade pattern is not normally sufficient to justify the existence of a factory pattern.
Choosing between a factory and directly instantiating instances depends on many factors:
- Does the factory use logic to process inputs (e.g. settings in the registry, data provided to the constructor)?
- add more here
Factories can exist as standalone classes, or as static methods inside the class for which they are providing instances.
Factories can simply duplicate what could be accomplished by __construct(), or they might provide a facade to reduce the complexity of creating intances. Also, factories allow factoring shared logic out of __construct()'s, thus keeping the code of the instance "pristine" and clean of distracting code that might not directly relate to the focus of that class.
Choosing between a callback and a plugin: