View Source

<ac:macro ac:name="note"><ac:parameter ac:name="title">draft</ac:parameter><ac:rich-text-body><p>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 <ac:emoticon ac:name="smile" /></p></ac:rich-text-body></ac:macro>

<h1>Motivation: Why use factory patterns?</h1>

<ul>
<li>Simplicity: simplify use of a plugin by encapsulating construction and management details exposed in the plugin API.</li>
<li>Automation: auto-find/load the plugin source file, and auto-instantiate.</li>
<li>Exceptions: throw exceptions in the factory on failure to load or instantiate the plugin.</li>
<li>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.</li>
</ul>


<h1>ZF Goals Relating to Factories</h1>

<ol>
<li>Set the Zend Framework standard and best practice for coding factories.</li>
<li>Support code analysis tools (e.g. parsing code to determine which methods are called where and by which classes, etc.)</li>
<li>Support direct instantiation of plugins via <code>new classname()</code>, when a factory pattern is not needed or desired by the developer</li>
<li>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</li>
<li>Switch: reduce the code required for mapping input choices to plugins (see &quot;Convenience Factories&quot; below).
<ac:macro ac:name="tip"><ac:rich-text-body><p>Directly instantiating classes wherever possible, practical, and reasonable improves the benefits of using tools like IDEs.</p></ac:rich-text-body></ac:macro></li>
</ol>


<p>Currently, the ZF has preferred using strings and mapping the strings to concrete classes within the factory -e.g. <code>$adapter = new Zend_Db('PDO_MYSQL', $optionsArray);</code></p>

<h1>ZF Proposed Coding Standard Addition</h1>

<p>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: <strong>consistency</strong></p>

<p>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 ('_').</p>

<p>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 &quot;word&quot; 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.</p>

<p>Factories ought to know by convention where to find the adapters, including a userland &quot;plugin&quot; directory, where developers can drop in their own, custom adapters.</p>

<h1>Types of Factories</h1>

<h2>Convenience Factories</h2>

<p>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.</p>

<p>If the factory method only encapsulates a &quot;switch&quot; contruct, then the factory can simply return an instance of the plugin.</p>

<ac:macro ac:name="code"><ac:parameter ac:name="title">Factory Switch Construct</ac:parameter><ac:plain-text-body><![CDATA[
switch($pluginType)
{
case PLUGIN_A: return new pluginA(); break;
case PLUGIN_B: return new pluginB(); break;
case PLUGIN_C: return new pluginC(); break;
default: throw new Zend_Exception('Unknown plugin type: '.$pluginType);
}
]]></ac:plain-text-body></ac:macro>

<p>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 <code>switch()</code> in the factory does not, by itself, result in significantly simpler code, and might actually result in duplication of the exception/error checking.</p>

<h2>Composition Factories</h2>

<p>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 <a href="http://www.martinfowler.com/articles/injection.html">Martin Fowler</a>.</p>

<p>A convenience factory might be used to create the plugin instance stored in the object returned by the composition factory.</p>

<p>Composition factories are equivalent to creating a common superclass from which plugins can inherit, but lack the &quot;switch construct&quot; and bookkeeping. Additionally, multiple or mixin style inheritance are needed if the plugins are not all related by a common superclass. </p>
<ac:macro ac:name="tip"><ac:rich-text-body><p>For simpler cases, using single inheritance with a common superclass often yields simpler code than using a composition factory.</p></ac:rich-text-body></ac:macro>

<h2>Factories vs. Plugins vs. Adapters vs. Direct Instantiation</h2>

<ac:macro ac:name="note"><ac:rich-text-body><p>This section needs some improvement.</p></ac:rich-text-body></ac:macro>

<p>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.</p>

<p>We prefer supporting code completion, when practical. </p>

<p>Simplicity is our mantra, so the default preference of the Zend Framework should take the form of:<br />
$instance = Zend_Class_Subclass($config_array); # direct</p>

<p>instead of:</p>

<p> $instance = Zend_Class_Factory('Subclass', $config_array); # needed for dependency injection</p>

<h1>Syntactical Ways to Achieve Factory Patterns</h1>

<ac:macro ac:name="code"><ac:parameter ac:name="title">Without Factory</ac:parameter><ac:plain-text-body><![CDATA[
require 'PluginName.php';
$pluginInstance = new Zend_PluginName();
]]></ac:plain-text-body></ac:macro>

<h2>Factory Methods Using Options Array</h2>

<ac:macro ac:name="code"><ac:parameter ac:name="title">Using Factory Methods</ac:parameter><ac:plain-text-body><![CDATA[
$pluginInstance = Factory::getInstance('Plugin_Name', $optionsArray); #1

$pluginInstance = Factory::getInstance(new Plugin($optionsArray)); #2

$pluginInstance = Factory::getInstance($optionsArray); #3

# We also need an algorithm for #1 and #3 to map the input data onto a set of plugins.
]]></ac:plain-text-body></ac:macro>

<h2>Factory Methods Using func_get_args()</h2>

<p>Instead of </p>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$options = array();
if ($flag1) $options['param1'] = 'something';
elseif ($flag2 || $flag3 && !$flag4) $options['param2'] = 'foobar';
]]></ac:plain-text-body></ac:macro>

<p>using <code>func_get_args()</code> allows us to do:</p>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$foo = new PluginClass(1,2);
$bar = FactoryClass::factory("PluginClass", 3,4);
]]></ac:plain-text-body></ac:macro>

<p>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 &quot;switch&quot; 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 &lt;keyword,value&gt; pairs, the developer will need something like the switch statement described in the &quot;convenience factories&quot; section.</p>

<ac:macro ac:name="code"><ac:parameter ac:name="title">How to Use funct_get_args() in Factory</ac:parameter><ac:plain-text-body><![CDATA[<?php
echo '$foo = new PluginClass(1,2);'."\n";
$foo = new PluginClass(1,2);
echo "\n*************\n";
echo '$foo = new PluginSubClass(1,2,3);'."\n";
$foo = new PluginSubClass(1,2,3);
echo "\n*************\n";
echo '$bar = FactoryClass::factory("PluginClass", 3,4);'."\n";
$bar = FactoryClass::factory("PluginClass", 3,4);
echo "\n*************\n";
echo '$bar = FactoryClass::factory("PluginSubClass", 3,4,5);'."\n";
$bar = FactoryClass::factory("PluginSubClass", 3,4,5);

abstract class FactoryClass {
/**
* Create an object
* @param mixed args ...
*/
function __construct() {
$args = func_get_args();
if(count($args)) {
// we've got a call with arguments
call_user_func_array(array($this, "_init"), $args);
}
}

static public function factory($name) {
$plugin = new $name();
$args = func_get_args();
array_shift($args);
// Factories often interact with plugins that do not inherit from the FactoryClass.
// Thus, _init() must be a public function.
call_user_func_array(array($plugin,"_init"), $args);
}
}


class PluginClass {
/**
* Create an object
* @param mixed args ...
*/
function __construct() {
$args = func_get_args();
if(count($args)) {
// we've got a call with arguments
call_user_func_array(array($this, "_init"), $args);
}
}

/**
* Initializer
*
* @param int $arg1 parameter 1
* @param int $arg2 parameter 2
* @param string $arg3 parameter 3
*/
public function _init($arg1, $arg2) {
// do something with $arg1, $arg2
echo "arg1=$arg1, arg2=$arg2";
}
}

class PluginSubClass extends PluginClass{
/**
* Initializer
*
* @param int $arg1 parameter 1
* @param int $arg2 parameter 2
* @param string $arg3 parameter 3
*/
public function _init($arg1, $arg2, $arg3) {
// do something with $arg1, $arg2, $arg3
echo "arg1=$arg1, arg2=$arg2, arg3=$arg3";
}
}
?>

Results:

$foo = new PluginClass(1,2);
arg1=1, arg2=2
*************
$foo = new PluginSubClass(1,2,3);
arg1=1, arg2=2, arg3=3
*************
$bar = FactoryClass::factory("PluginClass", 3,4);
arg1=3, arg2=4
*************
$bar = FactoryClass::factory("PluginSubClass", 3,4,5);
arg1=3, arg2=4, arg3=5
]]></ac:plain-text-body></ac:macro>

<h2>Ugly, but Works</h2>

<h3>Games with Strings</h3>
<ac:macro ac:name="code"><ac:parameter ac:name="title">Ugly, but works</ac:parameter><ac:plain-text-body><![CDATA[
$pluginName = "SomePlugin";
Zend::loadClass($pluginName); # not needed if using autoload, but best practices preclude autoload here
$pluginInstance = new "Factory$pluginName"($optionsArray);
]]></ac:plain-text-body></ac:macro>

<h3>Eval</h3>

<p>Some disadvantages of <code>eval</code>:</p>
<ol>
<li>Performance</li>
<li>Eval cannot benefit from bytecode caches and optimizations</li>
<li>Eval is very hard to debug</li>
</ol>


<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[<?php

class Factory
{
static public function createInstance($className)
{
/**
* @todo Class name validation must go here
*/

/**
* @todo Class loading logic could go here
*/

$args = func_get_args();
array_shift($args);

if ($args === null) {
return new $className();
}

$argsCount = count($args);
$argsString = '';
$comma = false;

for ($i = 0; $i < $argsCount; $i++) {
$argsString .= ($comma ? ', ' : '') . "\$args[$i]";
$comma = true;
}

return eval("return new $className($argsString);");
}
}

class ArgsNo
{
public function __construct()
{}
}

class ArgsYes
{
public function __construct($int, $bool, $string, $array, stdClass $obj)
{}
}

class ArgsOptional
{
public function __construct($first = 1, $second = 2)
{}
}

$argsNoNew = new ArgsNo();
$argsNoFactory = Factory::createInstance('ArgsNo');

$obj = new stdClass();
$argsYesNew = new ArgsYes(1, true, 'bar', array(4, 5, 6), $obj);
$argsYesFactory = Factory::createInstance('ArgsYes', 1, true, 'bar', array(4, 5, 6), $obj);

$argsOptionalNew = new ArgsOptional();
$argsOptionalFactory = Factory::createInstance('ArgsOptional');
]]></ac:plain-text-body></ac:macro>

<h1>Guidelines</h1>

<ac:macro ac:name="note"><ac:rich-text-body><p>This section needs some improvement.</p></ac:rich-text-body></ac:macro>

<ac:macro ac:name="info"><ac:rich-text-body><p>Guidelines are not as stringent or imperative as &quot;best practices&quot;, and depend more intimately with the specifics of each situation.</p></ac:rich-text-body></ac:macro>

<p>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.</p>

<p>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.</p>

<p>The facade pattern is not normally sufficient to justify the existence of a factory pattern.</p>

<p>Choosing between a factory and directly instantiating instances depends on many factors:</p>
<ul>
<li>Does the factory use logic to process inputs (e.g. settings in the registry, data provided to the constructor)?<br />
*</li>
<li>add more here</li>
</ul>



<p>Factories can exist as standalone classes, or as static methods inside the class for which they are providing instances.</p>

<p>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 &quot;pristine&quot; and clean of distracting code that might not directly relate to the focus of that class.</p>

<p>Choosing between a callback and a plugin:<br />
*<br />
*<br />
*</p>