So you’re learning Laravel you say? Well what is this IoC Container you speak of?! For a lot of us PHP programmers, we may not be familiar with this concept, but thanks to Laravel, proper software engineering is being brought into the PHP world. By getting a handle on what and how the IoC Container works in Laravel, we’ll have a better understanding of how Laravel provides the magic that it does when coding. Let’s take a closer look!
Inversion of Control
First up, just a little bit about Inversion of Control, in the generic sense.
Wikepedia provides us this explanation:
In software engineering, inversion of control (IoC) is a programming technique, expressed here in terms of object-oriented programming, in which object coupling is bound at run time by an assembler object and is typically not known at compile time using static analysis.
In traditional programming, the flow of the business logic is determined by objects that are statically assigned to one another. With inversion of control, the flow depends on the object graph that is instantiated by the assembler. Such a dynamic flow is made possible by object interactions being defined through abstractions. The binding process is achieved through dependency injection, although some argue that the use of a service locator also provides inversion of control.
What chu talkin bout Willis? 🙂 Well, that is a lot of verbiage to basically say, with good software engineering, we are going to make your life easier. How so you say? Let’s examine.
A container is just that, something that can hold things. In Laravel, the $app
object is your container. So it stands to reason that this $app
you speak of contains things, and if fact, it does. If you were to run this code
Route::get('/', function()
{
$app = app();
print_r($app);
});
You would see an ungodly amount of information dumped to the browser. The main thing to notice however is that it is an Object of IlluminateFoundationApplication
. The illuminate namespace contains all components of the Laravel framework. It’s an apropos name in as much as Laravel is bringing software design features from other enterprise languages into the world of PHP. Now, the Application class extends the Container class. It is from the Container class that wizardry emanates. Think of learning Laravel, not so much as an exercise in coding chops, but rather an expansion of your mind. It provides a new and different way of looking at problems and how to solve them with ingenious organization. In fact, Laravel challenges the very definition of a framework. At least that is how I think of it.
The Container class implements the ArrayAccess
interface. What this means is that you have flexibility when accessing your data. You can use what feels comfortable to you. For example you could use both of the following syntax versions with the same result:
$foo->bar = "baz";
$foo['bar'] = "baz";
Nifty!
The container is comprised of many Layers, or Components, such as Routing, Validation, Authentication, Cookie, Database, Encryption, Session, Filesystem, and many many more. The container neatly packages all of these components together to work in concert to provide you with PHP super powers!
Dependency Injection
Dependency Injection is great but it can become a pain to implement if you have to continuously instantiate and pass in dependencies but with the IoC container, we don’t have to worry about it.
So how does this all work in code? Well let’s whip up a few classes and see it in action. We’ll add a Car
, Tire
, and Engine
class. We know the App class extends the Container, so we have access to it’s methods. Let’s go ahead and bind our Car
class to the Container.
App::bind('Car', function()
{
return new Car;
});
Route::get('/', function()
{
dd(App::make('Car')); // resolve it
});
// object(Car)[146]
Let’s consider that a Car
depends on the Tire
and Engine
classes. How can we do this?
class Car {
protected $tire;
protected $engine;
public function __construct(Tire $tire, Engine $engine) {
$this->tire = $tire;
$this->engine = $engine;
}
}
class Tire {}
class Engine {}
App::bind('Car', function()
{
return new Car(new Tire, new Engine);
});
Route::get('/', function()
{
dd(App::make('Car')); // resolve it
});
// object(Car)[145]
// protected 'tire' =>
// object(Tire)[143]
// protected 'engine' =>
// object(Engine)[150]
Ok let’s change it up a little:
class Car {
protected $tire;
protected $engine;
public function __construct(Tire $tire, Engine $engine) {
$this->tire = $tire;
$this->engine = $engine;
}
}
class Tire {}
class Engine {}
Route::get('/', function()
{
dd(App::make('Car')); // resolve it
});
// Notice our binding is gone. What happens now?
// object(Car)[152]
// protected 'tire' =>
// object(Tire)[153]
// protected 'engine' =>
// object(Engine)[154]
In all of it’s goodness, Laravel will take the following actions for you:
- Did the user specifically create a binding for Car? (use it if so)
- What if the programmer didn’t specify one? Then reflect into Car to determine any dependencies.
- Resolve any dependencies needed by Car (Tire and Engine).
- Create the new instance for you, including any dependencies, Free of charge!
Wow. I wish I would have thought of that 🙂 If I had a glyphicon for a jaw drop, I would add it. In light of this we’ll go for a big thumbs up Thanks to Taylor Otwell and his team of superstars such as Dayle Rees, Shawn McCool, Jeffrey Way, Jason Lewis, Ben Corlett, Franz Liedke, Dries Vints, Mior Muhammad Zaki, and Phil Sturgeon for making all this cool stuff!
Dependencies of Dependencies
So let’s take this concept just a little further. We know our Car
has dependencies of a Tire
and an Engine
. What happens if the Tire
or Engine
have their own dependencies? Let’s say our Tire
has a Bridgestone
dependency, and the Engine
has a Turbo
dependency. We still just want to make a Car
, will our IoC take care of all of this for us?
Let’s see!
class Car {
protected $tire;
protected $engine;
public function __construct(Tire $tire, Engine $engine) {
$this->tire = $tire;
$this->engine = $engine;
}
}
class Tire {
protected $bridgestone;
public function __construct(Bridgestone $bridgestone) {
$this->bridgestone = $bridgestone;
}
}
class Engine {
protected $turbo;
public function __construct(Turbo $turbo) {
$this->turbo = $turbo;
}
}
class Bridgestone {
public $tread;
public function __construct(){
$this->tread = 'Performance';
}
}
class Turbo {
public $stage;
public function __construct(){
$this->stage = 2;
}
}
Route::get('/', function()
{
dd(App::make('Car')); // resolve it
});
// object(Car)[154]
// protected 'tire' =>
// object(Tire)[155]
// protected 'bridgestone' =>
// object(Bridgestone)[158]
// public 'tread' => string 'Performance' (length=11)
// protected 'engine' =>
// object(Engine)[157]
// protected 'turbo' =>
// object(Turbo)[160]
// public 'stage' => int 2
Holy Macaroni! Double thumbs up! The Container was able to work some real magic in resolving whatever the Car
needed.
In a nutshell, this is what the IoC container is for – it makes the task of setting up dependency injection much easier. Understanding the IoC Container is going to be very helpful once we start using the Laravel Repository Pattern to facilitate more readable and maintainable code.