Understanding IoC Containers and Dependency Injection

Quick Forward:

I'm writing this with the intention of getting a better understanding of dependency injection and IoC containers, but also so that afterwards I can correct the mistakes in it and use it to help teach a few friends of mine about them as well.

As of now I've tried reading through the documentation for various frameworks(laravel, fuel, codeigniter, symfony) and I found that there were too many different aspects of the frameworks that I needed to feel comfortable using it that I decided to try to just learn each of the major pieces individually on my own before trying to use them in the frameworks themselves.

I've spent hours googling various meanings, looking over stackoverflow responses, and reading various articles trying to understand what an IoC and how to use it to correctly manage dependencies, and I believe I understand what it is in concept, but I am still grey on how to implement it correctly. I think the best way for anybody reading this to help me is to give what my current understanding of IoC containers and dependency injection is, and then let people who have a better understanding than myself point out where my understanding falls short.

My understanding:

  • A dependency is when an instance of ClassA requires an instance of ClassB to instantiate a new instance of ClassA.
  • A dependency injection is when ClassA is passed an instance of ClassB, either through a parameter in ClassA's constructor or through a set~DependencyNameHere~(~DependencyNameHere~ $param) function. (This is one of the areas I'm not completely certain on).
  • An IoC container is a singleton Class(can only have 1 instance instantiated at any given time) where the specific way of instantiating objects of those class for this project can be registered. Here's a link to an example of what I'm trying to describe along with the class definition for the IoC container I've been using
  • So at this point is where I start trying use the IoC container for more complicated scenarios. As of now it seems in order to use the IoC container, I am limited to a has-a relationship for pretty much any class I want to create that has dependencies it wants to define in the IoC container. What if I want to create a class that inherits a class, but only if the parent class has been created in a specific way it was registered in the IoC container.

    So for example: I want to create a child class of mysqli, but I want to register this class in the IoC container to only instantiate with the parent class constructed in a way I've previously registered in the IoC container. I cannot think of a way to do this without duplicating code (and since this is a learning project I'm trying to keep it as 'pure' as possible). Here are some more examples of what I am trying to describe.

    So here are some of my questions:

  • Is what I'm trying to do above possible without breaking some principle of OOP? I know in c++ I could use dynamic memory and a copy constructor to accomplish it, but I haven't been able to find that sort of functionality in php. (I will admit that I have very little experience using any of the other magic methods besides __construct, but from reading and __clone if I understood correctly, I couldn't use in the constructor it to make the child class being instantiated a clone of an instance of the parent class).
  • Where should all my dependency class definitions go in relation to the IoC? (Should my IoC.php just have a bunch of require_once('dependencyClassDefinition.php') at the top? My gut reaction is that there is a better way, but I haven't come up with one yet)
  • What file should I be registering my objects in? Currently doing all the calls to IoC::register() in the IoC.php file after the class definition.
  • Do I need to register a dependency in the IoC before I register a class that needs that dependency? Since I'm not invoking the anonymous function until I actually instantiate an object registered in the IoC, I'm guessing not, but its still a concern.
  • Is there anything else I'm overlooking that I should be doing or using? I'm trying to take it one step at a time, but I also don't want to know that my code will be reusable and, most importantly, that somebody who knows nothing about my project can read it and understand it.
  • I know this is extremely long, and just wanted to give a thanks in advance to anybody who took the time to read it, and even more so to anybody shares their knowledge.


    Put simply (because it's not a problem limited to OOP world only), a dependency is a situation where component A needs (depends on) component B to do the stuff it's supposed to do. The word is also used to describe the depended-on component in this scenario. To put this in OOP/PHP terms, consider the following example with the obligatory car analogy:

    class Car {
    
        public function start() {
            $engine = new Engine();
            $engine->vroom();
        }
    
    }
    

    Car depends on Engine . Engine is Car 's dependency. This piece of code is pretty bad though, because:

  • the dependency is implicit; you don't know it's there until you inspect the Car 's code
  • the classes are tightly coupled; you can't substitute the Engine with MockEngine for testing purposes or TurboEngine that extends the original one without modifying the Car .
  • It looks kind of silly for a car to be able to build an engine for itself, doesn't it?
  • Dependency injection is a way of solving all these problems by making the fact that Car needs Engine explicit and explicitly providing it with one:

    class Car {
    
        protected $engine;
    
        public function __construct(Engine $engine) {
            $this->engine = $engine;
        }
    
        public function start() {
            $this->engine->vroom();
        }
    
    }
    
    $engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
    $car = new Car($engine);
    

    The above is an example of constructor injection , in which the dependency (the depended-on object) is provided to the dependent (consumer) through the class constructor. Another way would be exposing a setEngine method in the Car class and using it to inject an instance of Engine . This is known as setter injection and is useful mostly for dependencies that are supposed to be swapped at run-time.

    Any non-trivial project consists of a bunch of interdependent components and it gets easy to lose track on what gets injected where pretty quickly. A dependency injection container is an object that knows how to instantiate and configure other objects, knows what their relationship with other objects in the project are and does the dependency injection for you. This lets you centralize the management of all your project's (inter)dependencies and, more importantly, makes it possible to change/mock one or more of them without having to edit a bunch of places in your code.

    Let's ditch the car analogy and look at what OP's trying to achieve as an example. Let's say we have a Database object depending on mysqli object. Let's say we want to use a really primitive dependency indection container class DIC that exposes two methods: register($name, $callback) to register a way of creating an object under the given name and resolve($name) to get the object from that name. Our container setup would look something like this:

    $dic = new DIC();
    $dic->register('mysqli', function() {
        return new mysqli('somehost','username','password');
    });
    $dic->register('database', function() use($dic) {
        return new Database($dic->resolve('mysqli'));
    });
    

    Notice we're telling our container to grab an instance of mysqli from itself to assemble an instance of Database . Then to get a Database instance with its dependency automatically injected, we would simply:

    $database = $dic->resolve('database');
    

    That's the gist of it. A somewhat more sophisticated but still relatively simple and easy to grasp PHP DI/IoC container is Pimple. Check its documentation for more examples.


    Regarding OP's code and questions:

  • Don't use static class or a singleton for your container (or for anything else for that matter); they're both evil. Check out Pimple instead.
  • Decide whether you want your mysqliWrapper class extend mysql or depend on it.
  • By calling IoC from within mysqliWrapper you're swapping one dependency for another. Your objects shouldn't be aware of or use the container; otherwise it's not DIC anymore it's Service Locator (anti)pattern.
  • You don't need to require a class file before registering it in the container since you don't know if you're going to use an object of that class at all. Do all your container setup in one place. If you don't use an autoloader, you can require inside the anonymous function you register with the container.

  • Additional resources:

  • Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
  • Don't look for things -- a Clean Code Talk about IoC/DI
  • 链接地址: http://www.djcxy.com/p/82146.html

    上一篇: 带有InternalCall属性的C#内部静态外部

    下一篇: 了解IoC容器和依赖注入