Welcome Guest, Not a member yet? Register   Sign In
CInject - Dependency Injection Framework and IoC Container
#1

[eluser]TaylorOtwell[/eluser]
This version has been deprecated. Please check out version 2.0.0 in this thread.


What's the problem?

Note: If you are not familiar with the design practice of "Dependency Injection", please read this article.

Let's say we have the following class:

Code:
class User_repository {

     public function __construct(IPassword_hasher $hasher, IUser_mapper $mapper)
     {
          //
     }

}

Everytime we instantiate our User_repository, we have to give it an implementation of the IPassword_hasher and IUser_mapper interfaces. So, our instantiation might look like this:

Code:
$repository = new User_repository(new Sha1_hasher, new Base_user_mapper);

That works, but what if our User_repository needs another dependency? We would have to add that dependency to the constructor and also pass it in everywhere we instantiate User_repository in our code. Also, our code isn't very flexible because we are passing in concrete implementations every time we instantiate the object.

What's a solution?

We need something that can instantiate our objects for us, as well as manage their dependencies. One tool to accomplish this is an Inversion of Control Container (IoC container). That's where CInject steps in. With CInject, we can "bind" our interfaces to concrete implementations in one place, and let CInject manage them for us throughout our application.

So, using same example class as above, we can bind our dependencies with CInject and let it manage the dependencies for us. Bindings are defined in application/config/bindings.php like so:

Code:
CInject::container()->bind('IPassword_hasher')->to('Sha1_hasher');
CInject::container()->bind('IUser_mapper')->to('Base_user_mapper');

Alright, now that all of our dependencies are registered, we can instantiate the User_repository like this:

Code:
$repository = CInject::make('User_repository');

CInject will inspect the types of dependencies required by User_repository, and will "inject" the necessary implementations of those dependencies automatically. Our User_repository will automatically be given a Sha1_hasher and a Base_user_mapper! Even more, Sha1_hasher will have it's dependencies resolved as well, and those dependencies will have their dependencies resolved, etc.

Now, if we need to change IPassword_hasher implementations throughout our application, just change your IPassword_hasher binding to the new implementation and you're done!

Resolving Concrete Types

It is not necessary to bind concrete types in the container. In other words, if your classes do not use interfaces, you do not *have* to bind them in the container, unless you want a singleton instance to be managed for you (more on this below).

Singletons

I find the for most situations, binding an object as a singleton is perfectly fine, and it will give your better performance since the object does have to be instantiated every time it is needed.

You can bind objects as "singletons" like so:

Code:
CInject::container()->bind('IPassword_hasher')->to('Sha1_hasher')->as_singleton();

Or, if it is a concrete type you are binding:

Code:
CInject::container()->bind('Sha1_hasher')->to_self()->as_singleton();

Notes

- You must use Type Hinting to let CInject know what type your dependency is.

- The files containing your classes must be named the same as the class itself.

- By default, the files containing your classes must be somewhere in the application/models or application/libraries folders. Sub-directories are OK!

- It is possible to hack the core to allow dependency injection on your controllers. It should only take about 2 lines of code. Proceed at your own risk. But, in my opinion, this gives you the full power of CInject.
[/size]




Theme © iAndrew 2016 - Forum software by © MyBB