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
instead of:
$instance = Zend_Class_Factory('Subclass', $config_array); # needed for dependency injection
h1. Syntactical Ways to Achieve Factory Patterns
{code:title=Without Factory}
require 'PluginName.php';
$pluginInstance = new Zend_PluginName();
{code}
h2. Factory Methods Using Options Array
{code:title=Using Factory Methods}
$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.
{code}
h2. Factory Methods Using func_get_args()
Instead of {code}
$options = array();
if ($flag1) $options['param1'] = 'something';
elseif ($flag2 || $flag3 && !$flag4) $options['param2'] = 'foobar';
{code}
using {{func_get_args()}} allows us to do:
{code}
$foo = new PluginClass(1,2);
$bar = FactoryClass::factory("PluginClass", 3,4);
{code}
{code:title=How to Use funct_get_args() in Factory}<?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
{code}
h2. Ugly, but Works
h3. Games with Strings
{code:title=Ugly, but works}
$pluginName = "SomePlugin";
Zend::loadClass($pluginName); # not needed if using autoload, but best practices preclude autoload here
$pluginInstance = new "Factory$pluginName"($optionsArray);
{code}
h3. Eval
Some disadvantages of {{eval}}:
# Performance
# Eval cannot benefit from bytecode caches and optimizations
# Eval is very hard to debug
{code}<?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');
{code}