Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Object Creation and Configuration
- The ZF1 State of Things:
- 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.
- class Foo
- {
- public function __construct($config = array()) {}
- }
- The obvious pros:
- * easy of use since the array() is the most well known and versatile
- structure in PHP
- 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)
- The not so obvious cons are
- * 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.
- Side effects of objects trying to centralize configuration are:
- * 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)
- Important things to remember:
- * 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)
- What does this mean?
- 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.
- 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. **
- This means that if an object must have a name, then the
- constructor should be
- class Foo
- {
- public function __construct($name, $value = null) {}
- public function setValue($value) {}
- }
- * Factories should describe the source being used for object
- creation, for example:
- Baz::fromArray(array $array);
- Baz::fromConfig(Zend\Config\Config $config);
- Baz::fromString($string);
- // used in Zend\Code
- Baz::fromReflection(ReflectionFile $reflection);
- (etc)
- The from<source>() pattern should only be used when these methods
- exist within the class/type being constructed.
- This pattern is well defined on wikipedia, see the "Descriptive
- names" section:
- http://en.wikipedia.org/wiki/Factory_method_pattern
- It is understood that *all* 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):
- public function fromArray(array $array)
- {
- // constructor param setup from array
- // static will always apply to extending classes
- $obj = new static(/* req. params */);
- // other wiring from array
- return $obj; // will always return subtype
- }
- ** Important 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.
- Example:
- class Foo
- {
- protected $value;
- public static function fromArray($array)
- {
- $obj = new static;
- // interact with protected member
- $obj->value = $array['value'];
- return $obj;
- }
- public function getValue()
- {
- return $this->value;
- }
- }
- * Dynamic/object factories will be allowed when one object is
- creating objects of a different type. These methods should
- NOT be static. The name of this factory object should contain
- the name 'Factory', for example:
- class FooFactory
- {
- public function createBarFromArray(array $a) {
- // return type Bar
- }
- public function createBarFromConfig(config $b) {
- // return type Bar
- }
- }
- 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" factory
- object.
- ** This model should be only used in complex instantiation
- scenarios **
- * 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 $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.
- * Factories should throw exceptions when not enough information
- is provided.
- * Objects should be completely valid and ready to do their object
- after instantiation
- * All required dependencies should be fulfilled at instantiation
- time
- * The special factory: createDefaultInstance() should create a
- poka-yoke instance with all dependencies pre-configured with
- sane defaults. For example:
- class Foo
- {
- public static function createDefaultInstance()
- {
- return new static(new Bar, new Baz);
- }
- public function __construct(Bar $bar, Baz $baz) {}
- }
- Concerns Left To Other Components
- * 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.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement