10
votes

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.

4
"Dependency Injection" is a 25-dollar term for a 5-cent concept -- James ShoreShiplu Mokaddim

4 Answers

12
votes

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);
  }
}
31
votes

Dependency Injection !== DIC

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

The DIC is "magic cure", which 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 underlaying design issues.

If you want to learn more about Dependency Injection, i would recommend for you to watch the "Clean Code Talks" on youtube:

0
votes

I've wrote an article about this problem. The ideea is to use 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:

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 below
    }
}

The dependency container is the standard one. The client code could look something 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 bussiness. 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 happen, a lot of dependencies are detected, resolved and injected. Notice that I don't use the new operator to create $someObject. The responsability of actual creation of the object is passed to the AbstractFactory

P.S. Gica is my nickname :)

-6
votes

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);
    }
}