compared with
Current by Pádraic Brady
on Sep 08, 2011 17:47.

Key
This line was removed.
This word was removed. This word was added.
This line was added.

Changes (106)

View Page History
{composition-setup}
h1. RFC: better configuration for components
{note:title=RFC Status: For discussion}
This RFC is currently under discussion and is not complete.
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{composition-setup}{composition-setup}]]></ac:plain-text-body></ac:macro>
<h1>RFC: better configuration for components</h1>
<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.<br />
Please comment on this RFC on the mailing list.</p></ac:rich-text-body></ac:macro>
{note}
{info}
This RFC is an alternate approach (counter-RFC) to [Ralph's RFC|http://framework.zend.com/wiki/display/ZFDEV2/RFC+-+Object+instantiation+and+configuration].
{info}
<ac:macro ac:name="info"><ac:rich-text-body>
<p>This RFC is an alternate approach (counter-RFC) to <a href="http://framework.zend.com/wiki/display/ZFDEV2/RFC+-+Object+instantiation+and+configuration">Ralph's RFC</a>.</p></ac:rich-text-body></ac:macro>

h2. TOC
{toc:minlevel=2} <h2>TOC</h2>
<ac:macro ac:name="toc"><ac:parameter ac:name="minlevel">2</ac:parameter></ac:macro>

h2. Introduction
<h2>Introduction</h2>
<p>Currently, most ZF1/ZF2 components are instantiated using one of the following styles:</p>

h4. A <h4>A constructor that consumes an array of config options </h4>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$f = new Zend\Filter\Compress(array( ... ) );
{code}
]]></ac:plain-text-body></ac:macro>
h4. A <h4>A constructor that consumes 2 or more arrays for configuration:</h4>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$f = new Zend\Filter\InputFilter(
$filters,
$options
;
{code}
h4. A cocktail of scalars, arrays and objects
{code}
]]></ac:plain-text-body></ac:macro>
<h4>A cocktail of scalars, arrays and objects</h4>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$p = new Zend\Mail\Protocol\Smtp(
'127.0.0.1',
$options = null
);
{code}
h4. Any number of setters and getters for config options
{code}
]]></ac:plain-text-body></ac:macro>
<h4>Any number of setters and getters for config options</h4>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$application->setOptions(array( ... ) );
$application->hasOption('foo');
$db->setParams(array( ... ));
$alnumFilter->setAllowWhiteSpace(true);
{code}
h4. "Deaf" constructor (no parameters accepted) + setters
{code}
]]></ac:plain-text-body></ac:macro>
<h4>&quot;Deaf&quot; constructor (no parameters accepted) + setters</h4>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$amfServer = new Zend\Amf\Server();
$amfServer->setAuthService($auth);
$amfServer->setProduction(1);
$amfServer->setBroker($broker);
{code}
]]></ac:plain-text-body></ac:macro>

<p>The purpose of this thread is to try and normalize, simplify and unify the way we components in Zend Framework. This is becoming more important each as more and more components are ported to ZF2. We have also agreed on some form of public invitation for 3rd party developers to contribute additional components. A simplified, unified configuration style will help the framework cope with that.</p>


h2. Problems with current approach (ZF1)
# *what config params can I use?* - array() is convenient for passing large number of named parameters but because it is weak-typed (i.e. array can hold any keys and values) developer does not know what options are accepted and required. What should happen with unrecognized values? (currently, they are usually ignored which introduces confusion for the developer).
# *what is the correct format for param X?* - In order to learn about configurable parameters, user has to read through long constructor phpdoc description (if provided) or search the manual.
# *array performance* - array() parsing and validation requires looping over its contents and processing all values.
# *validation results inconsistencies* - Sometimes components throw an exception directly form constructor, sometimes during operation (when lazy-loading), sometimes they will just silently fail (return false?). Also - getters do not always cover all configuration parameters.
# *naming inconsistencies* - there are several different terms used in various components to describe configuration:
* _options_
* _parameters_
* _properties_
* _preferences_
<h2>Problems with current approach (ZF1)</h2>
<ol>
<li><strong>What config params can I use?</strong></li>
</ol>


h2. Goals:
Main goals of this proposal are:
# To decouple configuration from component functionality (separation of concerns)
# To unify the way we configure all components (common standard).
# To decrease learning curve.
# To make configuration more robust and explicit.
# To make configuration more IDE friendly.
<p>An array() is convenient for passing large number of named parameters but because it is weakly-typed (i.e. array can hold any keys and values), the developer does not know what options are accepted, required or optional. What should happen with unrecognized values? Currently, they are usually ignored which introduces confusion for the developer. There are also inconsistencies in how configuration keys are named, and how even their associated getters and setters (should then exist) are named. This increases the confusion by making it much harder to quickly intuit the correct configuration key name.</p>

h2. Non-goals
This proposal is *NOT* meant to :
# increase performance (in some scenarios it will, but that's not the goal)
# decrease code verbosity
# force better coding standards per se
# deprecate all {{__constructors}} and their parameters in all classes
<ol>
<li><strong>Code duplication and multiple class responsibilities</strong></li>
</ol>

{info}
This is a proposal for configuring components' main classes - those directly exposed to the end-user
{info}

h2. Using an object as a replacement for array
<p>For certain objects, the work is done by a network of helper objects within the same class hierarchy which may themselves be independently usable. Should any of these require bits of the configuration, the approaches can lead to duplication of getters, setters, validation logic and inconsistent methods of transporting configuration across the object boundaries. Where this imposes on one class the responsibility of managing configuration of other classes, it adds another responsibility which is in breach of the Single Responsibility Principle. Using a core Config object centralises all the configuration values, methods and operating logic in a single transportable unit.</p>

Let's consider using simple objects as a configuration containers. Here a very simple example:
<ol>
<li><strong>Array performance</strong></li>
</ol>

{deck:id=code1}
<p>Array parsing and validation requires looping over its contents and processing all values. This is performed any number of different ways across components creating another point of inconsistent behaviour.</p>

<ol>
<li><strong>Validation results inconsistencies</strong></li>
</ol>


<p>Sometimes components throw an exception directly form constructor, sometimes during operation (when lazy-loading), sometimes they will just silently fail (return false?). Also - getters do not always cover all configuration parameters. The result is that users have no particular guarantee that configuration is validated. In many cases, a bad configuration value is first communicating to the user when some basic PHP operation fails. This is poor practice. Objects should locate problems BEFORE calling PHP functions and class methods. It ensures rapid debugging of faulty configuration values with a helpful Exception and reduces the change of a faulty configuration value ending up in a Fatal Error scenario unexpectedly.</p>

<ol>
<li><strong>Terminology inconsistencies</strong></li>
</ol>


<p>There are several different terms used in various components to describe configuration:</p>
<ul>
<li><em>options</em></li>
<li><em>parameters</em></li>
<li><em>properties</em></li>
<li><em>preferences</em></li>
</ul>


<ol>
<li><strong>Configuration key naming</strong></li>
</ol>


<p>At present, while discussed previously, configuration keys are not always named consistently. A core Config object could implement a simple convention based approach in that it could map configuration keys to getter/setter method names in one consistent fashion. This allows configuration keys to be driven by getter/setter naming and actively discourage random key naming which makes intuitive key selection difficult.</p>


<h2>Goals:</h2>
<p>Main goals of this proposal are:</p>
<ol>
<li>To decouple configuration from component functionality (adheres to separation of concerns, single responsibility principle, law of demeter)</li>
<li>To unify the way we configure all components (common standard).</li>
<li>To decrease learning curve.</li>
<li>To make configuration more robust and explicit.</li>
<li>To make configuration more IDE friendly.</li>
</ol>


<h2>Non-goals</h2>
<p>This proposal is <strong>NOT</strong> meant to :</p>
<ol>
<li>increase performance (in some scenarios it will, but that's not the goal)</li>
<li>decrease code verbosity</li>
<li>force better coding standards per se</li>
<li>deprecate all <code>__constructors</code> and their parameters in all classes</li>
</ol>


<ac:macro ac:name="info"><ac:rich-text-body>
<p>This is a proposal for configuring components' main classes - those directly exposed to the end-user. It is not intended to be applied to all possible classes, though sub units such as adapters/plugins/helpers <strong>should</strong> accept a configuration object, where necessary, rather than altering the transport mechanism needlessly (e.g. calling a list of setters or transferring config to an array).</p></ac:rich-text-body></ac:macro>

<h2>Using an object as a replacement for array</h2>

<p>Let's consider using simple objects as a configuration containers. Here's a very simple example:</p>

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck:id=code1}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=real-world usage}]]></ac:plain-text-body></ac:macro>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$config = new RepeaterConfig;
$config->count = 40;
$repeater = new Repeater($config);
$repeater->doit();
{code}
{card}
{card:label=RepeaterConfig}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=RepeaterConfig}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class RepeaterConfig
{
public $text = '';
}
{code}
{card}
{card:label=Repeater}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=Repeater}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Repeater
{
}
}
{code}
{card}
{deck}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck}]]></ac:plain-text-body></ac:macro>

h4. Pros
* very IDE friendly
* self-explanatory
* all config parameters are named and well known
* all config parameters can be given default values
* accessors are evil^TM^ and should be avoided if possible (if not absolutely required)
<h4>Pros</h4>
<ul>
<li>very IDE friendly</li>
<li>self-explanatory</li>
<li>all config parameters are named and well known</li>
<li>all config parameters can be given default values</li>
<li>accessors are evil^TM^ and should be avoided if possible (if not absolutely required)</li>
* config <li>config object is referable, so we do not have to interact with the main component instance to change configuration (separation of concerns)</li>
</ul>


h2. Using getters and setters for validation

Now let's add validation for our config object. We are implementing SoC (Separation of Concerns) by moving the validation part out of the main class and into {{RepeaterConfig}} class. The main component should always receive a valid, pre-processed configuration so it can focus on real-world functionality.
<h2>Using getters and setters for validation</h2>

<p>Now let's add validation for our config object. We are implementing SoC (Separation of Concerns) and SRP (Single Responsibility Principle) by moving the validation part out of the main class and into <code>RepeaterConfig</code> class. The main component should always receive a valid, pre-processed configuration so it can focus on real-world functionality.</p>
{deck:id=code2}
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck:id=code2}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=real-world usage}]]></ac:plain-text-body></ac:macro>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$config = new RepeaterConfig;

$repeater->doit();

{code}
{card}
{card:label=RepeaterConfig}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=RepeaterConfig}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
/**
* @property int $count Number of times to repeat.
}
}
{code}
{card}
{card:label=Repeater}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=Repeater}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Repeater
{
}
}
{code}
{card}
{deck}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck}]]></ac:plain-text-body></ac:macro>

h4. Pros
* all of the previous pros
* still very IDE friendly (we are using standard {{@parameter}} to handle dynamic properties)
* validation is handled by {{RepeaterConfig}} instead of the functional, main component's class (separation of concerns)
* we can use either configuration style: properties or setters
<h4>Pros</h4>
<ul>
<li>all of the previous pros</li>
<li>still very IDE friendly (we are using standard <code>@parameter</code> to handle dynamic properties)</li>
<li>validation is handled by <code>RepeaterConfig</code> instead of the functional, main component's class (separation of concerns)</li>
<li>we can use either configuration style: properties or setters</li>
</ul>


h2. Backward compatibility

Let's implement constructor into our config object and main class, so if we wish to construct an instance from an array (knowing what config options are accepted) we can do that.
<h2>Backward compatibility</h2>

<p>Let's implement constructor into our config object and main class, so if we wish to construct an instance from an array (knowing what config options are accepted) we can do that.</p>
{deck:id=code3}
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck:id=code3}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=real-world usage}]]></ac:plain-text-body></ac:macro>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
// using config array
$repeater = new Repeater(array(
));
$repeater = new Repeater($config);
{code}
{card}
{card:label=RepeaterConfig}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=RepeaterConfig}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
/**
* @property int $count Number of times to repeat.
}
}
{code}
{card}
{card:label=Repeater}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=Repeater}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Repeater
{
}
}
{code}
{card}
{deck}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck}]]></ac:plain-text-body></ac:macro>

h4. Pros
* all before
* we can use array and any Iterable object for configuration (including Zend\Config)
* we can use any of the 3 config styles, fully BC.
<h4>Pros</h4>
<ul>
<li>all before</li>
<li>we can use array and any Iterable object for configuration (including Zend\Config)</li>
<li>we can use any of the 3 config styles, fully BC.</li>
</ul>


h2. DI, lazy-loading and instantiating depending classes
Because we are internally using setters, we can instantiate depending objects using any method imaginable.
Let's modify our configuration to digest a {{Zend\Filter}}. Let's also try to use DI to set up some defaults.

<h2>DI, lazy-loading and instantiating depending classes</h2>
<p>Because we are internally using setters, we can instantiate depending objects using any method imaginable.<br />
Let's modify our configuration to digest a <code>Zend\Filter</code>. Let's also try to use DI to set up some defaults.</p>
{deck:id=code4}
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck:id=code4}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=real-world usage}]]></ac:plain-text-body></ac:macro>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
// config using DI
$di = new Zend\Di\DependencyInjector;
));

{code}
{card}
{card:label=RepeaterConfig}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=RepeaterConfig}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
/**
* @property int $count Number of times to repeat.
}
}
{code}
{card}
{card:label=Repeater}
{code}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card:label=Repeater}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class Repeater
{
}
}
{code}
{card}
{deck}
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{card}]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{deck}]]></ac:plain-text-body></ac:macro>



h4. Pros
<h4>Pros</h4>
<ul>
* DI <li>DI is a great candidate to handle sub-instances, loading, both run-time and global configuration (defaults)</li>
* DI could be modified to "understand" config objects and read their parameters
* it is easy to implement things like optional/required parameters
* lazy-loading
<li>DI could be modified to &quot;understand&quot; config objects and read their parameters</li>
<li>it is easy to implement things like optional/required parameters</li>
<li>lazy-loading</li>
</ul>