10

I'm trying to get my head around Dependency Injection and I understand it, for the most part.

However, say if, for some reason, one of my classes was dependent on several classes, instead of passing all of these to this one class in the constructor, is there a better, more sensible method?

I've heard about DI Containers, is this how I would go about solving this problem? Where should I start with this solution? Do I pass the dependencies to my DIC, and then pass this to the class that needs these dependencies?

Any help that would point me in the right direction would be fantastic.

Azirius
  • 482
  • 1
  • 5
  • 13
  • 19
    `"Dependency Injection" is a 25-dollar term for a 5-cent concept` -- [James Shore](http://jamesshore.com/Blog/Dependency-Injection-Demystified.html) – Shiplu Mokaddim Apr 08 '12 at 18:03

4 Answers4

31

Dependency Injection !== DIC

People should really stop confusing them. Dependency Injection is an idea that comes from Dependency Inversion principle.

The DIC is a "magic cure" that promises to let you use dependency injection, but in PHP is usually implemented by breaking every other principle of object oriented programming. The worst implementations tend to also attach it all to global state, via static Registry or Singleton.

Anyway, if your class depends on too many other classes, then in general, it signifies a design flaw in the class itself. You basically have a class with too many reasons to change, thus, breaking the Single Responsibility principle.

In this case, then dependency injection container will only hide the underlying design issues.

If you want to learn more about Dependency Injection, I would recommend you to watch the "Clean Code Talks" on Youtube.

apaderno
  • 28,547
  • 16
  • 75
  • 90
tereško
  • 58,060
  • 25
  • 98
  • 150
11

If you have several dependencies to deal with, then yes a DI container can be the solution.

The DI container can be an object or array constructed of the various dependent object you need, which gets passed to the constructor and unpacked.

Suppose you needed a config object, a database connection, and a client info object passed to each of your classes. You can create an array which holds them:

// Assume each is created or accessed as a singleton, however needed...
// This may be created globally at the top of your script, and passed into each newly
// instantiated class
$di_container = array(
  'config' = new Config(),
  'db' = new DB($user, $pass, $db, $whatever),
  'client' = new ClientInfo($clientid)
);

And your class constructors accept the DI container as a parameter:

class SomeClass {
  private $config;
  private $db;
  private $client;
 
  public function __construct(&$di_container) {
    $this->config = $di_container['config'];
    $this->db = $di_container['db'];
    $this->client = $di_container['client'];
  }
}

Instead of an array as I did above (which is simple), you might also create the DI container as an class itself and instantiate it with the component classes injected into it individually. One benefit to using an object instead of an array is that by default it will be passed by reference into the classes using it, while an array is passed by value (though objects inside the array are still references).

Edit

There are some ways in which an object is more flexible than an array, although more complicated to code initially.

The container object may also create/instantiate the contained classes in its constructor as well (rather than creating them outside and passing them in). This can save you some coding on each script that uses it, as you only need to instantiate one object (which itself instantiates several others).

Class DIContainer {
  public $config;
  public $db;
  public $client;

  // The DI container can build its own member objects
  public function __construct($params....) {
    $this->config = new Config();

    // These vars might be passed in the constructor, or could be constants, or something else
    $this->db = new DB($user, $pass, $db, $whatever);

    // Same here -  the var may come from the constructor, $_SESSION, or somewhere else
    $this->client = new ClientInfo($clientid);
  }
}
Community
  • 1
  • 1
Michael Berkowski
  • 267,341
  • 46
  • 444
  • 390
  • Fantastic, that is exactly the answer I was expecting. Just one more thing, between an array or object of dependencies, which is better? – Azirius Apr 08 '12 at 18:10
  • 1
    @Azirius I added an example above where the object is more flexible. – Michael Berkowski Apr 08 '12 at 18:52
  • Great stuff. Thanks a lot for the help, you've cleared that up for me :-) – Azirius Apr 08 '12 at 18:59
  • 11
    That is quite a horrid implementation of DIC. Also , what's the point in passing array as reference , when you are not writing to it? – tereško Apr 08 '12 at 20:17
  • 1
    @tereško The array reference is precisely _in case_ it needs to be written to, imitating the behavior of an object injected instead of an array (preferred) – Michael Berkowski Apr 08 '12 at 21:39
  • 3
    Would you really write to a referenced array in the constructor ?! Thats insane. Or is it just because you don't understand copy-on-write in php5 .. – tereško Apr 08 '12 at 21:48
  • You should consider an existing PHP framework for dependency injection. I'm partial to [Diesel]( http://asheepapart.blogspot.com/2012/07/php-dependency-injection-part-2.html), mainly because I wrote it ;-) I've found that dealing with Registries or Containers leads to confusion and frustrating debugging. I agree with @tereško that you should not conflate the topic of Dependency Inversion. – Ben Jan 30 '13 at 05:44
  • 2
    NO , you shouldn't. Especially not something as horrible as your Diesel. – tereško Jan 30 '13 at 06:21
  • 4
    This is the service locator anti-pattern. Google it. – Jimbo May 23 '13 at 12:09
0

I've wrote an article about this problem. The idea is using a combination of abstract factory and dependency injection to achieve transparent dependency resolving of (possible nested) dependencies. I will copy/paste here the main code snippets.

namespace Gica\Interfaces\Dependency;
    
interface AbstractFactory
{
    public function createObject($objectClass, $constructorArguments = []);
}

The abstract factory implementation is the following.

namespace Gica\Dependency;
 
class AbstractFactory implements \Gica\Interfaces\Dependency\AbstractFactory, \Gica\Interfaces\Dependency\WithDependencyInjector
{
    use WithDependencyInjector;
   
    /**
     * @param string $objectClass
     * @param array $constructorArguments
     * @return object instanceof $class
     */
    public function createObject($objectClass, $constructorArguments =     [])
    {
        $instance = new $objectClass(...$constructorArguments);
  
        $this->getDependencyInjector()->resolveDependencies($instance);
    
        return $instance;
    }
}

The dependency injector is this.

namespace Gica\Dependency;

class DependencyInjector implements \Gica\Interfaces\Dependency\DependencyInjector
{
    use \Gica\Traits\WithDependencyContainer;
    
    public function resolveDependencies($instance)
    {
        $sm = $this->getDependencyInjectionContainer();
    
        if ($instance instanceof \Gica\Interfaces\WithAuthenticator) {
            $instance->setAuthenticator($sm->get(\Gica\Interfaces\Authentication\Authenticator::class));
        }
        if ($instance instanceof \Gica\Interfaces\WithPdo) {
            $instance->setPdo($sm->get(\Gica\SqlQuery\Connection::class));
        }
        
        if ($instance instanceof \Gica\Interfaces\Dependency\WithAbstractFactory) {
            $instance->setAbstractFactory($sm->get(\Gica\Interfaces\Dependency\AbstractFactory::class));
        }
        // All the dependency declaring interfaces go here.
    }
}

The dependency container is the standard one.

The client code could look like this.

$abstractFactory = $container->get(\Gica\Interfaces\Dependency\AbstractFactory::class);
    
$someHelper = $abstractFactory->createObject(\Web\Helper\SomeHelper::class);
    
echo $someHelper->helpAction();

Notice that dependencies are hidden, and we can focus on the main business. My client code doesn't care or know that $someHelper need an Authenticator or that helpAction() need an SomeObject to do its work.

In the background, a lot of things happens, a lot of dependencies are detected, resolved, and injected. Notice that I don't use the new operator to create $someObject. The responsibility of actual creation of the object is passed to the AbstractFactory.

apaderno
  • 28,547
  • 16
  • 75
  • 90
Constantin Galbenu
  • 16,951
  • 3
  • 38
  • 54
-6

I recommend you to use Singltones or Mutlitones. In these cases you will be always able to get objects via static class' methods.

The other way (couldn't find a correct pattern name, but it could be Registry) is to use one global static object to store multiple objects' instances. E.g. (simplified code, without any checks):

class Registry {
    private static $instances = array();

    public static function add($k, $v) {
        $this->instances[$k] = $v;
    }

    public static function get($k) {
        return $this->instances[$k];
    }
}

class MyClass {
    public function __construct() {
        Registry::add('myclass', $this);
    }
}
Botanick
  • 663
  • 5
  • 10
  • I have considered Singletons, however, I've read that DI is the way forward. – Azirius Apr 08 '12 at 18:20
  • DI is a good solution however, but IMO it has much more better realization in Java (i.e. [Automatically injected dependency](http://en.wikipedia.org/wiki/Dependency_injection#Automatically_injected_dependency)), not in PHP itself – Botanick Apr 08 '12 at 18:26
  • @RepWhoringPeeHaa Explain your opinion. – Botanick Apr 09 '12 at 04:56
  • 10
    @Botanick I believe I already did. Globals are bad and what you have in your code are fancy globals. I suggest you watch the videos in teresko's answer. – PeeHaa Apr 09 '12 at 14:09