Digging in to Laravel's IoC Container

Inversion of Control, or IoC, is a technique that allows control to be inverted when compared to classical procedural code. The most prominent form of IoC is, of course, Dependency Injection, or DI. Laravel's IoC container is one of the most used Laravel features, yet is probably the least understood.

Here's a very quick example of using Dependency Injection to achieve Inversion of Control.

By using constructor injection we have now delegated creation of our Petrol instance back to the caller itself, thus achieving inversion of control. Our JeepWrangler does not need to know where the Petrol comes from, so long as it gets it.

So what does all this have to do with Laravel? Quite a lot, actually. Laravel, if you didn't know, is actually an IoC container. A container is an object that, as you may expect, contains things. Laravel's IoC container is used to contain many, many different bindings. Everything you do in Laravel will at some point have an interaction with the IoC container. This interaction is generally in the form of a binding being resolved.

If you open up any of the existing Laravel service providers, you'll most likely see something like this in the register method (example has been simplified, a lot).

That is a very, very basic binding. It consists of the name of the binding (router) and a resolver (the closure). When that binding is resolved from the container we will get an instance of Router returned.

Laravel usually groups similar binding names, such as session and session.store.

To resolve the binding we can simply call a method directly off it, or use the make method on the container.

That is what the container does in its most basic form. But, like most things Laravel, there's a whole lot more to it than just binding and resolving classes.

Shared and Non-Shared Bindings

If you've looked through several of the Laravel service providers, you'll notice that most bindings are defined like the earlier example. Here it is again:

This binding uses the share method on the container. Laravel uses a static variable to store a previous resolved value and will simply reuse that value when a binding is resolved again. This is basically what the share method does.

Another way of writing this would be to use the bindShared method.

You can also use the singleton and instance methods to achieve a shared binding. So, if they all achieve the same thing, what's the difference? Not a whole lot, actually. I personally prefer to use the bindShared method.

Conditional Binding

There may be times when you want to bind something to the container, but only when it hasn't already been bound before. There are a few ways you could go about this, but the easiest is to use the bindIf method.

This will only bind to the container if the router binding does not already exist. The only thing to be aware of here is how to share a conditional binding. To do so, you need to supply a third parameter to the bindIf method with a value of true.

Automatic Dependency Resolution

One of the most commonly used features of the IoC container is its ability to automatically resolve dependencies for classes that are not bound. What does this mean, exactly? First off, we don't actually need to bind something to the container to resolve an instance. We can simply make an instance of just about any class.

The container will instantiate our Petrol class for us. The best part about this is that it will also resolve constructor dependencies for us.

The first thing the container does is inspect the dependencies of the JeepWrangler class. It will then attempt to resolve those dependencies. So, because our JeepWrangler type-hints the Petrol class, the container will automatically resolve and inject it as a dependency.

The container can't automatically inject non-type-hinted dependencies. So if one of your dependencies is an array, then you'll need to instantiate it manually or give the parameter a default value.

Binding Implementations

Having Laravel automatically resolve your dependencies is great and simplifies the process of instantiating classes manually. However, there are times when you want a specific implementation to be injected, especially when you're using interfaces. This is easily achieved by using the fully qualified name of the class as the binding. To demonstrate this we'll be using a new interface called Fuel.

Now our JeepWrangler class can type-hint the interface, and we'll make sure that our Petrol class implements the interface.

We can now bind our Fuel interface to the container and have it resolve a new instance of Petrol.

Now when we make a new instance of our JeepWrangler, the container will see that it asks for Fuel, and it will know to automatically inject an instance of Petrol.

This also makes it really easy to swap out an implementation, as we can simply change the binding in the container. To demonstrate, we might start refueling our car with premium petrol, which is a little more expensive.

Contextual Bindings

Note that contextual bindings are only available in Laravel 5.

A contextual binding allows you to bind an implementation (much like we did above) to a specific class.

We'll then create a new NissanPatrol class that extends the abstract class, and we'll update our JeepWrangler to extend it as well.

Lastly we'll create a new Diesel class that implements the Fuel interface.

Now, our Jeep Wrangler will refuel using petrol and our Nissan Patrol will refuel using diesel. If we tried to use the same method as before by binding an implementation to the interface, then both of these cars would get the same type of fuel, which isn't what we want.

So to make sure each car refuels with the correct fuel, we can inform the container which implementation to use in each context.

Tagging

Note that tagging is only available in Laravel 5.

Being able to resolve bindings from the container is pretty important. Normally we can only resolve something if we know how it's been bound to the container. With Laravel 5, we're now able to tag our bindings so that developers can easily resolve all bindings that have the same tag.

If you're developing an application that allows other developers to build plugins and you want to be able to easily resolve all of those plugins, tags will be extremely useful.

Now, to resolve all the bindings for a given tag we can use the tagged method.

Rebounds and Rebinding

When you bind something to the container with the same name more than once, it's called rebinding. Laravel will notice you binding something again and will trigger a rebound.

The biggest benefit here is when you're developing a package that allows other developers to extend it by rebinding components in the container. To use it we'll need to implement setter injection on our Car abstract.

Let's assume we're binding our JeepWrangler to the container like so.

This is perfectly fine, but let's say another developer comes along and wants to extend this and use premium petrol in the car. So they use the setFuel method to inject their new fuel into the car.

In most cases, this might be all that's needed; however, what if our package gets more complicated and the fuel binding is injected into several other classes? This would result in the other developer having to set their new instance a whole bunch of times. So, to solve this, we can make use of rebinding:

The rebinding method will immediately return to us the already bound instance so we're able to use it in the constructor of our JeepWrangler. The closure given to the rebinding method receives two parameters, the first being the IoC container and the second being the new binding. We can then use the setFuel method ourselves to inject the new binding into our JeepWrangler instance.

All that's left is for another developer to simply rebind fuel in the container. Their service provider might look like this:

Once the binding is rebound in the container, Laravel will automatically trigger the associated closures. In our case the new PremiumPetrol instance will be set on our JeepWrangler instance.

Extending

If you want to inject a dependency into one of the core bindings or a binding created by a package, then the extend method on the container is one of the easiest ways to go about it. 

This method will resolve the binding from the container and execute a closure with the container and the resolved instance as parameters. This allows you to easily resolve and inject your own bindings, or simply instantiate a new class and inject it.

Unlike rebinding, this will only set the dependency on a single binding.

Usage Outside Laravel

Like many of the Illuminate components that make up the Laravel framework, the Container can be used outside Laravel in a standalone application. To do so, you must first require it as a dependency in your composer.json file.

This will install the latest 4.2 version of the container. Now, all that's left to do is instantiate a new container.

Of all the components, this is one of the easiest to use wherever you need a flexible and featured IoC container.

Tags:

Comments

Related Articles