Laravel framework is a tool - a tool that helps you create a web application - a tool that helps people build businesses. To be able to completely utilize a tool and build powerful, large web applications, we have to understand how it works, what is under-the-hood and how it's all tied together.
In this article, we will take a look at the core of Laravel - the IoC Container.
"The Laravel inversion of control (IoC) container is a powerful tool for managing class dependencies." - Laravel official documentation.
There are 2 classes that we will be taking a look at in this article:
Illuminate\Container\Container.php
Illuminate\Foundation\Application.php
The Container::class
The Container::class
is a framework agnostic implementation of IoC container. This class is a singleton and is responsible for features such as binding and resolving classes out of the container. Because of this class, you can do things like binding a class:
// Binding a class to an interface..
app()->bind(FooInterface::class, Foo::class);
// Or to a string..
app()->bind('foo', Foo::class);
And resolving the instance of a class:
// Resolving using the interface..
app()->resolve(FooInterface::class); // You will get the instance of Foo::class
// Or using the string..
app()->resolve('foo'); // You will get the instance of Foo::class
The Container::class
is also responsible for auto-wiring class constructor dependencies by utilizing PHP's Reflection API.
See PHP Reflection API manual: https://www.php.net/manual/en/book.reflection.php
Because of this, gone are the days when you have to manually inject all dependencies just to get an instance of a class:
$foo = new Foo(
new Bar1, new Bar2(
new DeepDependency, new AnotherDeepDependency
// Imagine these classes having dependencies too..
)
);
You can literally just call the class out of the container and it will try to automatically inject the nested dependencies for you, as long as those dependencies are also resolvable out of the container.
app()->resolve(Foo::class); // Clean!
Aside from these, Container::class
is also responsible for more advanced features such as contextual binding, which is a topic for another article. But I encourage you to have a take a look at Container.php
class sometime. It's a single-file class with a bit more than 1.3k lines of code as of this writing, so its a pretty easy read.
If you are building your own PHP framework and need an implementation of IoC container, you can install the Illuminate\Container
package via Composer. See packagist link here: https://packagist.org/packages/illuminate/container
The Application::class
The Application::class
is Laravel's framework-specific implementation of the IoC container. This class simply inherits from the Container::class
, but with extra methods that were tailor-fitted for Laravel's architecture.
But how does the Application::class
fit into the overall flow of the framework?
Well, for me to explain that, lets take a quick look at the beginning of the Framework's request-lifecycle.
Once a request hits the web server, it will land onto the entry file of the framework which is the public/index.php
. Taking a look at the first 3 lines of codes of this file:
// From Laravel 7.18 (lower versions might have different codes)
// First line of code defines a constant, not much going on here
define('LARAVEL_START', microtime(true));
// 2nd line of code just pulls in the vendor/autoload.php file
// Typical if you are using package managers such as composer
require __DIR__.'/../vendor/autoload.php';
// 3rd line of code pulls in a bootstrap/app.php file and stores
// its value to a variable called `$app`. Interesting!
$app = require_once __DIR__.'/../bootstrap/app.php';
So, the 3rd line actually calls a framework specific file, which is the bootstrap/app.php
, lets take a look at this file:
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
The first thing that this file does is to create an instance of the Application::class
. And if you will read the respective comment for this line of code:
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
In other words, Application::class
ties all the Laravel components together. Components like system paths, Laravel service providers, and 3rd-party packages (this happens later on at the Kernel class). To prove that, lets take a quick look at what happens when we instantiate it:
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
Looks like, the first thing it does is to set the base path, taking a deeper look at that method it seems its also calling a bindPathsInContainer()
, which sets other important paths in the framework:
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}
And yes, the helper methods like storage_path()
, public_path()
that we learned to love so much are just calling these bindings! Pretty cool, right?
Next step is to register all base bindings such as binding the application itself to the container as a singleton - remember that the Container::class
class is a singleton but the Application::class
is not. It also binds packages such as "Mix" and "Filesystem" packages at this point.
Next is registering base service providers such as EventServiceProvider::class
, LogServiceProvider::class
and RoutingServiceProvider::class
.
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
And last but not the least is registering core aliases to the container. These aliases are representative of various Laravel components such as the "app", "db", "cache", "config", "files", "hash", "storage", etc. I will not bother including the codes here as it is quite long.
And there you have it, a quick dive at the Laravel's IoC Container! Don't forget to visit Laravel's official documentation for more info:
https://laravel.com/docs/7.x/container
Cheers!