-- Bruno B. B. Magalhaes <[email protected]> wrote
(on Saturday, 11 July 2009, 06:46 PM -0300):
> well, despite the fact that I love the idea of using dependency injection, as
> it's also a very nice concept/pattern, I really think that it's too much
> overkill for a big application,
"overkill for a big application"? I'd say it might potentially be
overkill for a *small* application, but even then, the various PHP DI
solutions I've been monitoring are so simple in their implementation and
API that I'd hardly call them "overkill" for any sized application...
> and creates another layer of complexity to maintain and code...
This is, to me, an invalid argument. DI is actually a tremendously
simple idea, both in concept and implementation. It can be as simple as
this:
class Bar {}
class Foo
{
protected $_bar;
public function __construct(array $options)
{
if (isset($options['bar'])) {
$this->setBar($options['bar']);
}
}
public function setBar(Bar $bar)
{
$this->_bar = $bar;
return $this;
}
public function getBar()
{
if (null === $this->_bar) {
$this->setBar(new Bar());
}
return $this->_bar;
}
}
The actual injection is then done in one of two ways:
$bar = new Bar();
// Manually, using the setter
$foo = new Foo();
$foo->setBar($bar);
// Or at construction time:
$foo = new Foo(array('bar' => $bar));
This is trivial.
DI *containers* are slightly more complex, but not much more so. Most
PHP DI containers I've seen allow you to specify either a configuration
array or to use a configuration file. In each case, you associate a
dependency name with a class, and then any other dependency arguments to
use:
$diContainer = new DiContainer(array(
'user' => array(
'class' => 'App_Model_User',
'arguments' => array(
array('mapper' => 'userMapper'),
),
),
'userMapper' => array(
'class' => 'App_Model_UserMapper',
'arguments' => array(
array('table' => 'userTable'),
),
),
'userTable' => array(
'class' => 'App_Model_DbTable_User',
'arguments' => array(
array('db' => 'database'),
),
),
'database' => array(
'class' => 'Zend_Db',
'method' => 'factory',
'arguments' => array(
'pdo_mysql',
array(
'hostname' => 'localhost',
'username' => 'me',
'password' => 'XXXXXXXXX',
'dbName' => 'my_cool_app',
),
),
),
));
Now, to get our user model:
$user = $diContainer->user;
And it now has all its dependencies, mapped all the way down. It takes a
little work creating the dependency map up front, but this work will
help you down the line in a number of ways:
* Explicit location to find all dependencies of the project
* Once defined, makes it easier to define alternate schemes for use
with testing or in different development environments
> Another point is that when thinking about modularity, the "new module" (or
> piece of code that performs or observes an action) should be completely self
> contained, or, put more properly, the "new module" should import to it's scope
> the classes, objects, variables and constants that it needs to work, not the
> other way around...
What you're describing is a service locator, which is another form of
registry.
When you use a registry to retrieve dependencies, you have a hard
dependency on the registry -- but it's very difficult to determine what
other dependencies the class has unless you use the registry simply to
lazy load dependencies within getters. Even so, there should be logic
that handles the situation that will inevitably arise of the dependency
not actually being *in* the registry.
Additionally, I'll note it once again: static registries are the enemy
of unit testing. Even one static registry means that you require extra
steps to setUp and tearDown your environment for each test case, and
requires code in your registry to reset state. It gets ugly really fast.
If you're going to go this route, I'd recommend doing something like the
following:
class Foo
{
// ...
public function setBar(Bar $bar)
{
$this->_bar = $bar;
return $this;
}
public function getBar()
{
if (null === $this->_bar) {
if (Registry::has('bar')) {
$this->setBar(Registry::get('bar'));
} else {
$this->setBar(new Bar());
}
}
return $this->_bar;
}
}
This allows for the trifecta:
* Usage of a static global registry
* Usage of DI (because you have explicit getters and setters)
* Lazy-loading as a fallback (usually the default case)
> So passing the objects around with __construct, or setter methods,
> would require that, if the new piece of code requires a new object
> that it's not yet available/passed, the subjacent code be re-factored
> as well, and that's not an appealing idea I must say.
See my above discussion. DI is usually used with lazy-loading for the
default use cases. When it cannot be, you inject manually -- but
typically you *do* know the objects necessary before you use them.
> On Jul 11, 2009, at 5:30 PM, Matthew Ratzloff wrote:
>
>
> I would like to point out that early on in the development of
> Zend_Registry
> I had advocated providing the option of using it via dependency injection,
> but was summarily overruled. ;-)
>
> -Matt
>
> On Sat, Jul 11, 2009 at 1:21 PM, Ralph Schindler
> <[email protected]>
> wrote:
>
> This is effectively a named registry. I advocate the usage of named
> registries as a way to be able to group instances together into
> collections at named locations.
>
> You could similarly, do this with your own class:
>
> class My_Application {
> static function get($name);
> static function set($name, $value);
> }
>
> as opposed to
>
> function Application($name) { .. }
>
>
> the only difference is one solution is using an OO paradigm whereas
> the
> other is using a functional paradigm. The result is the same.
>
> I like advocating this b/c you are always 1 method call away from
> having the instance you want, which makes programming easy to write
> and
> easy to understand.
>
> OTHER solutions posed by my peers would be a Dependency Injection
> Container (you can google this). This has the added benefit of
> resolving dependecies as well as "automatic wiring" of instance. The
> downside is its a little heavier, and a little more complex (ie: takes
> more time to understand). But in larger code-bases, it has its pros.
> Also, it does make things easier to test against as static state does
> not get in the way as much.
>
> -ralph
>
>
>
> Bruno B. B. Magalhaes wrote:
>
> Hi everybody,
>
> first of all, this is my first post on this list, but I've been
> using this framework for a long time, since it's very early
> stages,
> and it has been very fun since...
>
> Yesterday I was working in a new client's infrastructure (we have
> to by contract) for a very large company in Brazil, and I had a
> simple idea when using a registry with variable scopes... For
> example the controller can only access the instances of database,
> router, request and response, the plugin can only use the
> configuration, database, request and response objects... How to
> deal with this kind of variable scope registry... Here is my
> suggestion:
>
> function Application($Name = NULL, $Instance = NULL)
> {
> static $Classes = array();
> static $Instances = array();
> if(empty($Name))
> {
> throw new Application_Exception('Unable to utilize this
> instance (`'.$Name.'`): The supplied instance name must not be
> empty.');
> }
>
> if(is_object($Instance))
> {
> $Classes[get_class($Instance)] = $Name;
>
> $Instances[$Name] = $Instance;
> return $Instance;
> }
> elseif(isset($Instances[$Name]))
> {
> return $Instances[$Name];
> }
> elseif(isset($Instances[$Classes[$Name]]))
> {
> return $Instances[$Classes[$Name]];
> }
> else
> {
> throw new Application_Exception('Unable to utilize this
> instance (`'.$Name.'`): It was not found within this specific
> scope
> (`application`).');
> }
> }
>
> For the controller scope:
>
> function Controller($Name = NULL, $Instance = NULL)
> {
> static $Classes = array();
> static $Instances = array();
> if(empty($Name))
> {
> throw new Application_Controller_Exception('Unable to
> utilize this instance (`'.$Name.'`): The supplied instance name
> must not be empty.');
> }
>
> if(is_object($Instance))
> {
> $Classes[get_class($Instance)] = $Name;
>
> $Instances[$Name] = $Instance;
> return $Instance;
> }
> elseif(isset($Instances[$Name]))
> {
> return $Instances[$Name];
> }
> elseif(isset($Instances[$Classes[$Name]]))
> {
> return $Instances[$Classes[$Name]];
> }
> else
> {
> throw new Application_Controller_Exception('Unable to
> utilize this instance (`'.$Name.'`): It was not found within this
> specific scope (`controller`).');
> }
> }
>
> And for the plugin scope
>
> function Plugin($Name = NULL, $Instance = NULL)
> {
> static $Classes = array();
> static $Instances = array();
> if(empty($Name))
> {
> throw new Application_Plugin_Exception('Unable to utilize
> this instance (`'.$Name.'`): The supplied instance name must not
> be
> empty.');
> }
>
> if(is_object($Instance))
> {
> $Classes[get_class($Instance)] = $Name;
>
> $Instances[$Name] = $Instance;
> return $Instance;
> }
> elseif(isset($Instances[$Name]))
> {
> return $Instances[$Name];
> }
> elseif(isset($Instances[$Classes[$Name]]))
> {
> return $Instances[$Classes[$Name]];
> }
> else
> {
> throw new Application_Plugin_Exception('Unable to utilize
> this instance (`'.$Name.'`): It was not found within this specific
> scope (`plugin`).');
> }
> }
>
> We would add instances to the registry like:
> Application('Configuration', new Zend_Config());
> Controller('Configuration', Application('Configuration'));
> Plugin('Configuration', Application('Configuration'));
>
> So we are able to use it like this, inside of a controller:
>
> Application('Configuration')->GerParameter('application.address');
> OR
> Controller('Configuration')->GerParameter('application.address');
>
> Os if this is available inside the plugin, we can use:
>
> Application('Configuration')->GerParameter('application.address');
> OR
> Plugin('Configuration')->GerParameter('application.address');
>
> Maybe we could have a function called Zend(), to "abstract" the
> Zend Registry? It makes the code extremely readable and fluid...
> Guys, am I crazy? Is this a pattern of somehow? :D
>
> Best regards,
> Bruno B B Magalhaes
>
>
>
>
>
--
Matthew Weier O'Phinney
Project Lead | [email protected]
Zend Framework | http://framework.zend.com/