>
> I've been pushing the setOptions/setConfig paradigm for a while, and it
>
works really well with DI. Your constructor then passes the options on
>
to setOptions() which attempts to call setters named after the keys. The
>
result is that it allows you to push your dependencies in at
>
instantiation -- leaving the constructor needing to simply check for
>
required settings (if any) only.
>
The primary problem with configuration in Zend Framework is that
configuration is left up to each class and so each class handles it
differently. The primary problem with PHP is lack of mixins, which would
elegantly solve the first problem. I solved the issue at my job like this:
<?php
class My_Config extends Zend_Config
{
/**
* Configures an object.
*
* @param object|string $caller Calling object or class name (for
static usage)
* @param Zend_Config|array $config Configuration
* @param string $section Config section, if using Zend_Config
* @return array Array representation of options, after taking section
into
* account if applicable
*/
public static function setConfig($caller, $config, $section = null)
{
$config = self::getOptionsFromConfig($config, $section);
foreach ($config as $option => $value) {
$method = 'set' . ucfirst($option);
if (method_exists($caller, $method)) {
if ($value instanceof Zend_Config) {
$value = $value->toArray();
}
if (is_object($caller)) {
$caller->$method($value);
} else {
call_user_func(array($caller, $method), $value);
}
}
}
return $config;
}
/**
* Returns an options array from a config.
*
* @param object|string $caller Calling object or class name (for
static usage)
* @param Zend_Config|array $config Configuration
* @param string $section Config section, if using Zend_Config
* @return array Array representation of options, after taking section
into
* account if applicable
*/
public static function getOptionsFromConfig($config, $section = null)
{
if ($config instanceof self) {
if ($section !== null) {
if ($config->{$section} === null) {
throw new My_Config_Exception("No configuration in
section '{$section}'");
}
$config = $config->{$section};
}
$config = $config->toArray();
}
return $config;
}
}
Each configurable class then has its own setConfig() method that proxies to
My_Config::setConfig() and perhaps sets up its constructor in the following
way, if it makes sense:
/**
* Constructor.
*
* @param Zend_Config|array $config (Optional) Configuration
* @param string $section (Optional) Config section
*/
public function __construct($config = null, $section = null)
{
if ($config !== null) {
$this->setConfig($config, $section);
}
}
/**
* Sets parameters from a Zend_Config instance.
*
* @param Zend_Config|array $config (Optional) Configuration
* @param string $section (Optional) Config section
* @return My_Class $this
*/
public function setConfig($config, $section = null)
{
My_Config::setConfig($this, $config, $section);
return $this;
}
This is a pretty flexible approach that allows instance or static method
calls on the caller and does not distinguish between Zend_Config objects and
arrays. In my experience there is no practical benefit, and some drawbacks,
to distinguishing between separate setConfig() and setOptions() methods. If
you must store the configuration in the object, for example to allow a
generic getOption($optionName) method, the logic is still off-loaded to the
other class.
Anyway, that's my two cents.
-Matt
On Tue, Jan 13, 2009 at 1:38 PM, Matthew Weier O'Phinney
<[email protected]>wrote:
> -- Benjamin Eberlei <[email protected]> wrote
> (on Tuesday, 13 January 2009, 06:11 PM +0100):
> > great post ralph! this is a step into a brighter future ;-D
> >
> > my two cents though.
> >
> > 1. having __construct(array $options); is cool from a simplicity point
> but it
> > hides dependencies, since in an array you couldinclude ANYTHING. Most
> > components also don't require the dependencies to be set in constructor,
> they
> > take all sort of options and set them but don't raise an exception when a
> > depdenency xyz misses.
> >
> > When you have a sensible default depdenency this would make sense though.
>
> It would be the job of the constructor to ensure that required options
> are passed, then -- or to provide defaults (potentially through an
> overloaded accessor that lazyloads a default).
>
> > ezcComponents uses constructor struct objects for configuration. You
> would have
> > an object Zend_Controller_Config which enforces some variables and is
> given to
> > the constructor. I like this alot, since objects always allow for more
> checks
> > on variables than arrays.
>
> However, this adds another dependency to using the objects -- you then
> need a value object before you can even use the object. If we allow
> either an array or a Zend_Config object, we give more flexibility in
> userland code for how the options are retrieved and passed to the
> constructors (they could be subtrees of an application-wide
> configuration object, or a serialized JSON array stored in memcached,
> etc).
>
> > 2. component registries are a great thing IF they are not singletonized.
> They
> > allow for type checking and consistency checks where the Zend_Registry
> does
> > not.
>
> If you look closely at Ralph's example, you'll notice it wasn't static.
> :) He and I have discussed this a fair bit, and the idea is that
> factories and other "gateway" classes would push themselves and/or the
> registry down into the subcomponent objects.
>
> > You could always make a container go into Zend_Registry to make it
> global, but
> > this should be optional, and pleeease let us just forget the singleton
> > antipattern. :-)
> >
> > If component registries are used we should make sure that all
> dependencies are
> > overwritable with them, for example the PluginLoader of Zend_Controller
> for
> > helpers is hardcoded into the front controller. The component registry
> should
> > include all objects that might impliciltly be set deep in the object
> graph of
> > the component. That would be super!
> >
> > 3. Implicit dependencies and work in the Constructor
> >
> > Dependency Injection is also about not having implicit dependencies or
> much
> > work in the constructor. We should evaluate all components based on this.
> > Having a "new Something()" inside an object constructor is evil, aswell
> as
> > doing lots of stuff that you cannot influence.
>
> I've been pushing the setOptions/setConfig paradigm for a while, and it
> works really well with DI. Your constructor then passes the options on
> to setOptions() which attempts to call setters named after the keys. The
> result is that it allows you to push your dependencies in at
> instantiation -- leaving the constructor needing to simply check for
> required settings (if any) only.
>
>
> > On Tuesday 13 January 2009 16:55:57 Ralph Schindler wrote:
> > > Comments inline:
> > > > The better solution is dependency injection, and one facet of this is
> > > > removing
> > >
> > > I disagree (if you are talking about DI as a container model)
> > >
> > > > explicit references to Registry/global items from source code. This
> is a
> > >
> > > I agree.
> > >
> > > > feature the ZF has yet to include but Bradley Holt has ressurected
> the
> > > > idea in the form of Zend_Container (
> > > >
> http://framework.zend.com/wiki/display/ZFPROP/Zend_Container+-+Bradley+Ho
> > > >lt ) and I really really hope it, or an evolution of it
> (Zend_Application
> > > > plugin?),
> > >
> > > I agree
> > >
> > > > makes it into the library. The last time something like this was
> proposed
> > > > it was bogged down by ever expanding requirements and complexity -
> > >
> > > True, as DI Containers tend to get.
> > >
> > > > simpler is better.
> > >
> > > +1000 here :) .. Now onto the explanation.
> > >
> > > To sum it up, I do think we need a solution here that will fit the
> > > framework, specifically our components- framework wide, as well as
> > > application wide.
> > >
> > > But, DI Containers (to me) have always introduced a level of
> indirection
> > > that is extremely uncommon for PHP. There are two ideas, I would love
> to
> > > see explored, and both of them would be rooted in what I would call a
> > > "component development convention", but only at the most forward facing
> API
> > > layer.
> > >
> > > First, the __construct($options = array()) {} convention for the most
> > > forward facing component API constructor adds a level of consistency
> that
> > > makes each and every component look and feel similar in style. This
> would
> > > makes it easy to move from component to component and have a good
> > > understanding of what is expected. Zend_Form does this, and to me, it
> make
> > > for the most flexible of components. In each case, $options can be a
> > > Zend_Config object, and assoc array of configuration values which can
> then
> > > be pushed down into the varios setXXX() methods, or $options can be the
> > > single most well used value, (if this were a Zend_Service component, it
> > > could be the API key).
> > >
> > > I believe this convention is very much used in Solar and its becoming
> more
> > > popular in ZF as we see more components hit the library.
> > >
> > > Second, I propose we look at creating a Registry class that can be
> extended
> > > (maybe its just a glorified ArrayObject), that can be used and defined
> by
> > > each component. This would allow components to have a *Component Level
> > > Registry*. How would this help? Well, instead of there being a
> dbAdapter
> > > key in the Application space registry, Zend_Db_Registry would contain a
> > > getDefaultAdapter() method. It might also have a getAdapter($name)
> method.
> > > Also, now the Zend_Db component can manage is component level registry
> and
> > > made the best decisions on how to lazyload and expose its individual
> pieces
> > > to the application layer, or user layer.
> > >
> > > For Zend_Controller, the api might look like this:
> > >
> > >
> > > Class Zend_Controller_Registry
> > > {
> > > public function getFront();
> > > public function getDispatcher();
> > > public function getRouter();
> > > public function getRequest();
> > > public function getResponse();
> > > /* .. Others .. */
> > > }
> > >
> > > Again, this delegate the responsibility down into the actual component
> of
> > > wiring dependencies, and either throwing an exception or lazy-loading
> when
> > > a non-explictily-set wiring is needed.
> > >
> > > This also alleviates the need for a god-like container to find, or be
> given
> > > a wiring diagram (which could become expensive for instantiation of
> every
> > > PHP object needed) to be able to work within all components and between
> all
> > > components. ALSO (if this is more of a convention
> Zend_Controller_Registry
> > > extends ArrayObject), this would reduce the intra-component
> dependencies.
> > >
> > > Also, PHP is not java, so containers need to be rebuilt on each
> request,
> > > whereas in Java the container might need to be built once and put into
> > > resident memory for all requests.
> > >
> > > Those are two ideas I wanted to float ever since hearing of DI. Im
> sure
> > > this will turn in to a blog post, since its more of a how does DI
> affect
> > > PHP sort of question.
> > >
> > > Let me know what you think.
> > >
> > > -ralph
> >
> > --
> > Benjamin Eberlei
> > http://www.beberlei.de
> >
>
> --
> Matthew Weier O'Phinney
> Software Architect | [email protected]
> Zend Framework | http://framework.zend.com/
>