compared with
Current by Ralph Schindler
on Sep 06, 2011 22:15.

(show comment)
Key
This line was removed.
This word was removed. This word was added.
This line was added.

Changes (109)

View Page History
h1. RFC: Object Creation and Configuration
<h1>RFC: Object Creation and Configuration</h1>

{note:title=RFC Status: For discussion}
<ac:macro ac:name="note"><ac:parameter ac:name="title">RFC Status: For discussion</ac:parameter><ac:rich-text-body>
<p>This RFC is currently under discussion and is not complete.</p>

<p>Please comment on this RFC on the mailing list.</p></ac:rich-text-body></ac:macro>
{note}

h2. The ZF1 State of Things:
<h2>The ZF1 State of Things:</h2>

<p>Over the development of the past few components in ZF2, we've been exploring different patterns that deal with object creation and configuration, as both of these concerns go hand-in-hand for most PHP developers - give the more common styles of coding. In ZF1, the most prolific pattern is the unified constructor.</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Foo
{
public function __construct($config = array()) {}
}
{code}
]]></ac:plain-text-body></ac:macro>

h3. The obvious pros:
<h3>The obvious pros:</h3>

<ul>
* easy <li>easy of use since the array() is the most well known and versatile structure in PHP</li>
</ul>

h3. The obvious cons:

* knowing what keys are valid for $config
* knowing what keys are required vs. option in $config
* key naming convention is not standardized
* docs cannot be generated from this prototype/signature by existing tools
* hard to understand what is a scalar value vs. an objects dependency (another object)
<h3>The obvious cons:</h3>

h3. The not so obvious cons are
<ul>
<li>knowing what keys are valid for $config</li>
<li>knowing what keys are required vs. option in $config</li>
<li>key naming convention is not standardized</li>
<li>docs cannot be generated from this prototype/signature by existing tools</li>
<li>hard to understand what is a scalar value vs. an objects dependency (another object)</li>
</ul>

* objects do not have a known identity; meaning, without knowing what instantiation time values distinguish one object from another.

* objects throughout the framework are too concerned with instantiation of other objects (dependencies) in non-obvious locations: like inside a getter or a setter.
<h3>The not so obvious cons are</h3>

h3. Side effects of objects trying to centralize configuration are:
<ul>
<li>objects do not have a known identity; meaning, without knowing what instantiation time values distinguish one object from another.</li>
</ul>

* Objects assume that they need to be configured early-on with configuration, but utilized later - leading developers to add lazy loading of dependencies as a feature of the object itself. This has the side effect of pushing creation of other objects into a getter or a setter in some form.
** (Lazy loading of dependencies should be only done by objects that are computationally expensive and/or part of the objects "graph building" strategy)

h3. Important things to remember:
<ul>
<li>objects throughout the framework are too concerned with instantiation of other objects (dependencies) in non-obvious locations: like inside a getter or a setter.</li>
</ul>

* constructors are not subject to Liskov Substitution Principle (even though PHP allows __construct() in an interface, having it there is considered bad practice and should be avoided anyway)

h4. What does this mean?
<h3>Side effects of objects trying to centralize configuration are:</h3>

It means that any subclass can change the signature of the constructor should be allowed as per the requirements of the sub-type. Since sub-types can change their constructor to suit their own requirements, forcing them to comply with a parents __construct($config = array()) should generally be considered a bad practice.
<ul>
<li>Objects assume that they need to be configured early-on with configuration, but utilized later - leading developers to add lazy loading of dependencies as a feature of the object itself. This has the side effect of pushing creation of other objects into a getter or a setter in some form.
<ul>
<li>(Lazy loading of dependencies should be only done by objects that are computationally expensive and/or part of the objects &quot;graph building&quot; strategy)</li>
</ul>
</li>
</ul>

h2. What is the proposal?

* Well named factories plus constructors that describe an objects hard dependencies / required values, and optional dependencies should be used.
* *Objects with no hard or soft dependencies would not have constructors.*
<h3>Important things to remember:</h3>

This means that if an object must have a name, then the constructor should be
<ul>
<li>constructors are not subject to Liskov Substitution Principle (even though PHP allows __construct() in an interface, having it there is considered bad practice and should be avoided anyway)</li>
</ul>

{code}
<h4>What does this mean? </h4>

<p>It means that any subclass can change the signature of the constructor should be allowed as per the requirements of the sub-type. Since sub-types can change their constructor to suit their own requirements, forcing them to comply with a parents __construct($config = array()) should generally be considered a bad practice. </p>

<h2>What is the proposal?</h2>

<ul>
<li>Well named factories plus constructors that describe an objects hard dependencies / required values, and optional dependencies should be used.</li>
<li><strong>Objects with no hard or soft dependencies would not have constructors.</strong></li>
</ul>


<p>This means that if an object must have a name, then the constructor should be</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Foo
{
public function setValue($value) {}
}
{code}
]]></ac:plain-text-body></ac:macro>

<ul>
* Factories <li>Factories should describe the source being used for object creation, for example:</li>
</ul>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
Baz::fromArray(array $array);
Baz::fromConfig(Zend\Config\Config $config);
Baz::fromReflection(ReflectionFile $reflection);
(etc)
{code}
]]></ac:plain-text-body></ac:macro>

The from<source>() <p>The from&lt;source&gt;() pattern should only be used when these methods exist within the class/type being constructed.</p>

This pattern is well defined on wikipedia, see the "Descriptive names" section: http://en.wikipedia.org/wiki/Factory_method_pattern
<p>This pattern is well defined on wikipedia, see the &quot;Descriptive names&quot; section: <a class="external-link" href="http://en.wikipedia.org/wiki/Factory_method_pattern">http://en.wikipedia.org/wiki/Factory_method_pattern</a></p>

<p>It is understood that *all* <strong>all</strong> factories within that given object will always produce type used at call time. This is achieved through PHP's 5.3 LSB (the factory applies to subtypes):</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
public static function fromArray(array $array)
{
return $obj; // will always return subtype
}
{code}

{note}
This takes advantage of PHP's class level visibility, this means that the factories can interact with instance protected properties without having to go through accessors/mutators.
{note}
]]></ac:plain-text-body></ac:macro>

Example:
<ac:macro ac:name="note"><ac:rich-text-body>
<p>This takes advantage of PHP's class level visibility, this means that the factories can interact with instance protected properties without having to go through accessors/mutators.</p></ac:rich-text-body></ac:macro>

{code} <p>Example:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Foo
{

}
{code}
]]></ac:plain-text-body></ac:macro>

<ul>
* Dynamic/object <li>Dynamic/object factories will be allowed when one object is creating objects of a different type. These methods should *NOT* <strong>NOT</strong> be static. The name of this factory object should contain the name 'Factory', for example:</li>
</ul>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class FooFactory
{
}
}
{code}
]]></ac:plain-text-body></ac:macro>

<p>The reasoning for having a factory object over a class full of static factory methods is that since one has opted to have a dynamic factory, there is some elements of factory configuration or state tracking that the factory is doing (for example, using a short name based plugin loader). Since that is the case, it is important that this state not be static so that other consumers of this factory have a fair chance at having a "default" &quot;default&quot; factory object.</p>

{note}This model should be only used in complex instantiation scenarios{note}
<ac:macro ac:name="note"><ac:rich-text-body><p>This model should be only used in complex instantiation scenarios</p></ac:rich-text-body></ac:macro>

<ul>
* Factories <li>Factories are capable of calling factories of similar source type. So for example, if Foo::fromArray($array) was called, and a particular key 'bar' is located in $array, where $Foo->setBar(Bar $Foo-&gt;setBar(Bar $bar), and it is established that Bar::fromArray() exists, Foo::fromArray() would use Bar::fromArray() to instantiate from the value of the 'bar' key. This solves the problem of nested configuration/arrays that model the configuration of an object graph.</li>
</ul>


<ul>
* Factories <li>Factories should throw exceptions when not enough information is provided.</li>
</ul>


<ul>
* Objects <li>Objects should be completely valid and ready to do their object after instantiation</li>
</ul>
* All required dependencies should be fulfilled at instantiation time


<ul>
<li>All required dependencies should be fulfilled at instantiation time</li>
</ul>


<ul>
* The <li>The special factory: createDefaultInstance() should create a poka-yoke instance with all dependencies pre-configured with sane defaults. For example:</li>
</ul>
{code}

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Foo
{

}
{code}
]]></ac:plain-text-body></ac:macro>

h2. Concerns Left To Other Components
<h2>Concerns Left To Other Components</h2>

<ul>
* Lazy <li>Lazy loading is not something any one object should be concerned with. Within an application, lazy loading can be achieved by the usage of a Service Locator. In other environments, this can also be solved by using a Dependency Injection container. See the above note on the special "createDefaultInstance()" factory. &quot;createDefaultInstance()&quot; factory.</li>
</ul>

h2. Configuration Keys:

Since array based factories are localized and not spread out amongst the class, the source for the keys are localized as well, which means we can utilize a combination of techniques to automate the finding and understanding of key values. First, they can be found inside a docblock. Second, they can be scanned by a docblock scanner and then formatted for usage in API docs, manual docs, etc. Here is an example of such code:
<h2>Configuration Keys:</h2>

<p>Since array based factories are localized and not spread out amongst the class, the source for the keys are localized as well, which means we can utilize a combination of techniques to automate the finding and understanding of key values. First, they can be found inside a docblock. Second, they can be scanned by a docblock scanner and then formatted for usage in API docs, manual docs, etc. Here is an example of such code:</p>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
namespace Foo;

}
}
{code}
]]></ac:plain-text-body></ac:macro>