Grigorash Vasilij
Grigorash Vasilij

Reputation: 713

Dependency Injection: pulling required components when they are actually needed

The gist behind DI is to relieve a class from creating and preparing objects it depends on and pushing them in. This sounds very reasonable, but sometimes a class does not need all the objects, that are being pushed into it to carry out its function. The reason behind this is an "early return" that happens upon invalid user input or an exception thrown by one of the required objects earlier or the unavailability of a certain value necessary to instantiate an object until a block of code runs.

More practical examples:

So, in a way pushing in all necessary components contradicts "lazy-loading" in the way that some components are created and never used, that being a bit unpractical and impacting performance. As far as PHP is concerned - more files are loaded, parsed and compiled. This is especially painful, if the objects being pushed in have their own dependencies.

i see 3 ways around it, 2 of which don't sound very well:

The question is what's the best way of dealing with such situations / what do you guys use?

UPDATE: @GordonM here are the examples of 3 approaches:

//inject factory example
interface IFactory{
    function factory();
}
class Bartender{
    protected $_factory;

    public function __construct(IFactory $f){
        $this->_factory = $f;
    }
    public function order($data){
        //validating $data
        //... return or throw exception
        //validation passed, order must be saved
        $db = $this->_factory->factory(); //! factory instance * num necessary components
        $db->insert('orders', $data);
        //...
    }
}

/*
inject provider example
assuming that the provider prepares necessary objects
(i.e. injects their dependencies as well)
*/
interface IProvider{
    function get($uid);
}
class Router{
    protected $_provider;

    public function __construct(IProvider $p){
        $this->_provider = $p;
    }
    public function route($str){
        //... match $str against routes to resolve class and method
        $inst = $this->_provider->get($class);
        //...
    }
}

//inject callback (old fashion way)
class MyProvider{
    protected $_db;
    public function getDb(){
        $this->_db = $this->_db ? $this->_db : new mysqli();
        return $this->_db;
    }
}
class Bartender{
    protected $_db;

    public function __construct(array $callback){
        $this->_db = $callback;
    }
    public function order($data){
        //validating $data
        //... return or throw exception
        //validation passed, order must be saved
        $db = call_user_func_array($this->_db, array());
        $db->insert('orders', $data);
        //...
    }
}
//the way it works under the hood:
$provider = new MyProvider();
$db = array($provider, 'getDb');
new Bartender($db);

//inject callback (the PHP 5.3 way)
class Bartender{
    protected $_db;

    public function __construct(Closure $callback){
        $this->_db = $callback;
    }
    public function order($data){
        //validating $data
        //... return or throw exception
        //validation passed, order must be saved
        $db = call_user_func_array($this->_db, array());
        $db->insert('orders', $data);
        //...
    }
}
//the way it works under the hood:
static $conn = null;
$db = function() use ($conn){
    $conn = $conn ? $conn : new mysqli();
    return $conn;
};
new Bartender($db);

Upvotes: 18

Views: 774

Answers (4)

Matthieu Napoli
Matthieu Napoli

Reputation: 49703

I chose lazy-injection (i.e. injecting a Proxy class):

class Class1 {
    /**
     * @Inject(lazy=true)
     * @var Class2
     */
    private $class2;

    public function doSomething() {
        // The dependency is loaded NOW
        return $this->class2->getSomethingElse();
    }

Here, the dependency (class2) is not injected directly: a proxy class is injected. Only when the proxy class is used that the dependency is loaded.

This is possible in PHP-DI (dependency injection framework).

Disclaimer: I work in this project

Upvotes: 0

GordonM
GordonM

Reputation: 31780

One idea that did occur was that of a proxy object. It implements the same interface(s) as the actual object you want to pass in, but instead of implementing anything it just holds an instance of the real class and forwards method calls on to it.

interface MyInterface 
{
    public function doFoo ();
    public function isFoo ();
    // etc
}

class RealClass implements MyInterface
{
    public function doFoo ()
    {
         return ('Foo!');
    }

    public function isFoo ()
    {
        return ($this -> doFoo () == 'Foo!'? true: false);
    }

    // etc
}

class RealClassProxy implements MyInterface
{
    private $instance = NULL;

    /**
     * Do lazy instantiation of the real class
     *
     * @return RealClass
     */
    private function getRealClass ()
    {
        if ($this -> instance === NULL)
        {
            $this -> instance = new RealClass ();
        }
        return $this -> instance;
    }

    public function doFoo ()
    {
        return $this -> getRealClass () -> doFoo ();
    }

    public function isFoo ()
    {
        return $this -> getRealClass () -> isFoo ();
    }

    // etc
}

Because the proxy has the same interface as the real class, you can pass it as an argument to any function/method that type hints for the interface. The Liskov Substitution Principle holds for the proxy because it responds to all the same messages as the real class and returns the same results (the interface enforces this, at least for method signitures). However, the real class doesn't get instantiated unless a message actually gets sent to the proxy, which does lazy instantiation of the real class behind the scenes.

function sendMessageToRealClass (MyInterface $instance)
{
    $instance -> doFoo ();
}

sendMessageToRealClass (new RealClass ());
sendMessageToRealClass (new RealClassProxy ());

There is an extra layer of indirection involved with the proxy object, which obviously means that there is a small performance hit for every method call you make. However, it does allow you to do lazy instantiation, so you can avoid instantiating classes you don't need. Whether this is worth it depends on the cost of instantiating the real object versus the cost of the extra layer of indirection.

EDIT: I had originally written this answer with the idea of subclassing the real object so you could use the technique with objects that don't implement any interfaces such as PDO. I had originally thought that interfaces were the correct way to do this but I wanted an approach that didn't rely on the class being tied to an interface. On reflection that was a big mistake so I've updated the answer to reflect what I should have done in the first place. This version does mean, however, that you can't directly apply this technique to classes with no associated interface. You'll have to wrap such classes in another class that does provide an interface for the proxy approach to be viable, meaning yet another layer of indirection.

Upvotes: 5

tereško
tereško

Reputation: 58454

If you want to implement lazy loading you basically have two way to do it (as you have already written in the topic):

  1. instead of injecting an instance of object you might need, you inject a Factory or a Builder. The difference between them is that instance of Builder is made for returning one type of object (maybe with different setups), while Factory makes different types of instances ( with same lifetime and/or implementing same interface ).

  2. utilize anonymous function which will return you an instance. That would look something like this:

    $provider = function() {
        return new \PDO('sqlite::memory:');
    };
    

    Only when you call this anonymous function, the instance of PDO is created and connection to database established.

What I usually do in my code is combine both. You can equip the Factory with such provider. This, for example, lets you have a single connection for all the objects which where created by said factory, and the connection is create only, when you first time ask an instance from Factory.

The other way to combine both methods (which i have not used, though) would be to create full blow Provider class, which in constructor accepts an anonymous function. Then the factory could pass around this same instance of Provider and the expensive object (PHPExcel, Doctrine, SwiftMailer or some other instance) is only created once a Product from that Factory first time turns to the Provider (couldn't come up with better name to describe all objects created by same factory) and requests it. After that, this expensive object is shared between all Products of Factory.

... my 2 cents

Upvotes: 3

GordonM
GordonM

Reputation: 31780

I've been thinking about this problem a lot lately in planning of a major project that I want to do as right as humanly possible (stick to LoD, no hard coded dependencies, etc). My first thought was the "Inject a factory" approach as well, but I'm not sure that's the way to go. The Clean Code talks from Google made the claim that if you reach through an object to get the object you really want then you're violating the LoD. That would seem to rule out the idea of injecting a factory, because you have to reach through the factory to get what you really want. Maybe I've missed some point there that makes it okay, but until I know for sure I'm pondering other approaches.

How do you do the function injection? I'd imagine you're passing in a callback that does the instantiation of the object you want, but a code example would be nice.

If you could update your question with code examples of how you do the three styles you mentioned it might be useful. I'm especially keen to see "injecting the injector" even if it is an antipattern.

Upvotes: 7

Related Questions