As developers, we are always trying to find new ways to write well designed and clean code by adopting new styles, using design patterns, and trying new robust frameworks. In this article we will explore the dependency injection design pattern through Laravel’s IoC component and see how it can improve our design.
Dependency Injection
Dependency injection is a term coined By Martin Fowler, and it’s the act of injecting components into your application. Like Ward Cunningham said:
Dependency Injection is a key element of agile architecture.
let’s see an example:
class UserProvider{
protected $connection;
public function __construct(){
$this->connection = new Connection;
}
public function retrieveByCredentials( array $credentials ){
$user = $this->connection
->where( 'email', $credentials['email'])
->where( 'password', $credentials['password'])
->first();
return $user;
}
}
If you want to test or maintain this class, you would have to access a real database and do some queries. To avoid having to do that and to decouple the class from the rest, you have one of three options to inject the Connection
class without actually using it directly.
When injecting components into your class you can use one of the three options:
Constructor Injection
class UserProvider{
protected $connection;
public function __construct( Connection $con ){
$this->connection = $con;
}
...
Setter Injection
Similarly we can inject our dependency using a setter method:
class UserProvider{
protected $connection;
public function __construct(){
...
}
public function setConnection( Connection $con ){
$this->connection = $con;
}
...
Interface Injection
interface ConnectionInjector{
public function injectConnection( Connection $con );
}
class UserProvider implements ConnectionInjector{
protected $connection;
public function __construct(){
...
}
public function injectConnection( Connection $con ){
$this->connection = $con;
}
}
When a class implements our interface, we define the injectConnection method to resolve the dependency.
Advantages
Now, when testing our class we can mock the dependency class and pass it as a parameter. Each class must be focused on a particular task and should not be concerned with resolving their dependencies. That way, you will have a better focused and maintainable application.
If you’d like to know more about DI, Alejandro Gervassio covered it extensively and professionally in this series, so be sure to give these articles a read. Now, what about IoC? Ioc (Inversion of control) is not necessary to use dependency injection, but it can help you to manage your dependencies effectively.
Inversion Of Control
Ioc is a simple component that makes resolving dependencies more convenient. You describe your objects to the container, and every time you resolve a class, the dependencies are injected automatically.
Laravel Ioc
Laravel Ioc is somehow special with its way of resolving dependencies, when you ask for an object:
We will use a simple example that we will improve during this article.
The SimpleAuth
class has a dependency of FileSessionStorage
, so our code might look like this:
class FileSessionStorage{
public function __construct(){
session_start();
}
public function get( $key ){
return $_SESSION[$key];
}
public function set( $key, $value ){
$_SESSION[$key] = $value;
}
}
class SimpleAuth{
protected $session;
public function __construct(){
$this->session = new FileSessionStorage;
}
}
//creating a SimpleAuth
$auth = new SimpleAuth();
This is the classic way of doing it, let’s start by using the constructor injection.
class SimpleAuth{
protected $session;
public function __construct( FileSessionStorage $session ){
$this->session = $session;
}
}
Now we create our object:
$auth = new SimpleAuth( new FileSessionStorage() );
Now I want to use Laravel Ioc to manage all of that.
Because the Application
class extends the Container
class, you can always access the container via the App
facade.
App::bind( 'FileSessionStorage', function(){
return new FileSessionStorage;
});
The first parameter for the bind
method is a unique id to bind to the container, the second is a callback function to be executed each time we resolve the FileSessionStorage
class, we can also pass a string representing class name as we will see next.
Note: if you inspect Laravel packages you will that sometimes bindings are grouped like ( view
, view.finder
..).
Let’s say that maybe we want to switch our session storage to MySql, our class should be similar to:
class MysqlSessionStorage{
public function __construct(){
//...
}
public function get($key){
// do something
}
public function set( $key, $value ){
// do something
}
}
Now that we have changed the dependency, we need to change the SimpleAuth
constructor and bind a new object to the container!
High level modules should not depend upon low level modules. Both
should depend upon abstractions.
Abstractions should not depend upon details. Details should depend
upon abstractions.Robert C. Martin
Our SimpleAuth
class should not be concerned about how our storage is done, instead it should focus more on just consuming the service.
So we can abstract our storage implementation:
interface SessionStorage{
public function get( $key );
public function set( $key, $value );
}
This way we can just implement and ask for an instance of the SessionStorage
interface:
class FileSessionStorage implements SessionStorage{
public function __construct(){
//...
}
public function get( $key ){
//...
}
public function set( $key, $value ){
//...
}
}
class MysqlSessionStorage implements SessionStorage{
public function __construct(){
//...
}
public function get( $key ){
//...
}
public function set( $key, $value ){
//...
}
}
class SimpleAuth{
protected $session;
public function __construct( SessionStorage $session ){
$this->session = $session;
}
}
If we try to resolve the SimpleAuth
class through the container using App::make('SimpleAuth')
, the container will throw a BindingResolutionException
, after trying to resolve the class from the bindings, falling back to the reflection method and resolving all the dependencies.
Uncaught exception 'Illuminate\Container\BindingResolutionException' with message 'Target [SessionStorage] is not instantiable.'
The container is trying to instantiate the interface. We can fix that by creating a specific binding for our interface.
App:bind( 'SessionStorage', 'MysqlSessionStorage' );
Now every time we try to resolve the interface through the container, we will get a MysqlSessionStorage
instance. If we want to switch our storage service we can just update the bindings.
Note: if you want to see if a class is bound to the container you can use App::bound('ClassName')
or use the App::bindIf('ClassName')
to register a binding if it hasn’t already been registered.
Laravel Ioc also offers App::singleton('ClassName', 'resolver')
for shared bindings.
You can also use App::instance('ClassName', 'instance')
to create a shared instance.
If the container can’t resolve the dependency it will throw a ReflectionException
, but we can use the App::resolvingAny(Closure)
to resolve any given type or as a form of fallback.
Note: if you register a resolver for a given type, the resolvingAny
method will be also called, but the value from the bind
method is returned.
Final Tips
- Where to store bindings:
If you have just a small application you can use yourglobal/start.php
, but if your project is getting larger you must use a service provider. - Testing:
When you’re just testing you need to consider usingphp artisan tinker
, it’s very powerful, and can increase your Laravel testing workflow. - Reflection API:
The PHP Reflection API is very powerful and if you want to understand the Laravel Ioc you need to get familiar with the Reflection API, be sure to check this tutorial for more information.
Conclusion
As always, the best way to learn about something is inspecting the source code. Laravel Ioc is just one file and shouldn’t take you long to go through all the functions. Would you like to know more about Laravel IoC or IoC in general? Let us know!
Frequently Asked Questions on Dependency Injection in Laravel’s IOC
What is the main purpose of Dependency Injection in Laravel’s IOC?
Dependency Injection in Laravel’s IOC (Inversion of Control) is a design pattern that allows for the decoupling of hard-coded dependencies. This means that instead of having your objects creating a dependency or asking a factory object to make one for them, you pass the needed dependencies into the object externally. This makes your code more flexible, reusable, and easier to test since you can control the dependencies from outside the class.
How does Laravel’s IOC container work?
Laravel’s IOC container is a powerful tool for managing class dependencies. It controls how different objects are resolved and created. When a class has dependencies, the container automatically injects them when the class is instantiated. This is done through a process called “auto-wiring,” where the container inspects the class to determine the dependencies automatically.
How can I bind a service to the Laravel’s IOC container?
To bind a service to Laravel’s IOC container, you can use the bind method. This method accepts two arguments: the class or interface name that will be used when resolving the service, and a closure that returns an instance of the class. The closure will receive the container instance, allowing you to resolve any other dependencies needed to instantiate the class.
What is the difference between bind and singleton in Laravel’s IOC container?
The difference between bind and singleton in Laravel’s IOC container lies in how instances are managed. When you bind a service, a new instance of the service will be created each time you resolve it. On the other hand, when you use singleton, the same instance will be returned every time the service is resolved.
How can I resolve a service from Laravel’s IOC container?
To resolve a service from Laravel’s IOC container, you can use the make method. This method accepts the name of the service to resolve as its argument. If the service has been bound to the container, it will return an instance of the service, with all its dependencies automatically injected.
How does Dependency Injection improve testing in Laravel?
Dependency Injection improves testing in Laravel by making your code more flexible and decoupled. This means you can easily swap out dependencies with mock objects during testing. This makes it easier to isolate the code under test and to control the test environment.
Can I use interface binding in Laravel’s IOC container?
Yes, you can use interface binding in Laravel’s IOC container. This allows you to bind an interface to a given implementation. Then, whenever the interface is requested, the container will inject the bound implementation.
How does Laravel’s IOC container handle automatic resolution?
Laravel’s IOC container handles automatic resolution by using reflection to inspect the dependencies of a class. When you attempt to resolve a class, the container will automatically build and inject all the dependencies the class needs.
What is a service provider in Laravel’s IOC container?
A service provider in Laravel’s IOC container is a way to group related IoC registrations in a single location. They are the central place to configure your application. Every Laravel application includes a number of service providers out of the box for core services.
How can I register a service provider in Laravel’s IOC container?
To register a service provider in Laravel’s IOC container, you can add it to the providers array in the config/app.php configuration file. Once the service provider is registered, it will be bootstrapped by Laravel when the application is bootstrapped.
Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.