Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • injecting a database connection object that will never be used, because the user data does not pass validation (provided that no triggers are used to validate this data)
  • injecting excel-like objects (PHPExcel e.g.) that collect input (heavy to load and instantiate because a whole library is pulled in and never used, because validation throws an exception earlier than a write occurs)
  • a variable value that is determined within a class, but not the injector at runtime; for instance, a routing component that determines the controller (or command) class and method that should be called based on user input
  • although this might be a design problem, but a substantial service-class, that depends on a lot of components, but uses only like 1/3 of them per request (the reason, why i tend to use command classes instead of controllers)

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:

  • injecting a factory
  • injecting the injector (an anti-pattern)
  • injecting some external function, that gets called from within the class once a relevant point is reached (smtg like "retrieve a PHPExcel instance once data validation finished"); this is what i tend to use due to its flexibility

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);
like image 624
Grigorash Vasilij Avatar asked May 14 '12 18:05

Grigorash Vasilij


1 Answers

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.

like image 120
GordonM Avatar answered Sep 29 '22 21:09

GordonM