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 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:
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>$this->connection = new Connection; </span> <span>} </span> <span>public function retrieveByCredentials( array $credentials ){ </span> <span>$user = $this->connection </span> <span>->where( 'email', $credentials['email']) </span> <span>->where( 'password', $credentials['password']) </span> <span>->first(); </span> <span>return $user; </span> <span>} </span><span>}</span>
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:
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span> <span>...</span>
Similarly we can inject our dependency using a setter method:
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>... </span> <span>} </span> <span>public function setConnection( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span> <span>...</span>
<span>interface ConnectionInjector{ </span> <span>public function injectConnection( Connection $con ); </span><span>} </span> <span>class UserProvider implements ConnectionInjector{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>... </span> <span>} </span> <span>public function injectConnection( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span><span>}</span>
When a class implements our interface, we define the injectConnection method to resolve the dependency.
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.
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 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:
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>$this->connection = new Connection; </span> <span>} </span> <span>public function retrieveByCredentials( array $credentials ){ </span> <span>$user = $this->connection </span> <span>->where( 'email', $credentials['email']) </span> <span>->where( 'password', $credentials['password']) </span> <span>->first(); </span> <span>return $user; </span> <span>} </span><span>}</span>
This is the classic way of doing it, let’s start by using the constructor injection.
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span> <span>...</span>
Now we create our object:
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>... </span> <span>} </span> <span>public function setConnection( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span> <span>...</span>
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.
<span>interface ConnectionInjector{ </span> <span>public function injectConnection( Connection $con ); </span><span>} </span> <span>class UserProvider implements ConnectionInjector{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>... </span> <span>} </span> <span>public function injectConnection( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span><span>}</span>
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:
<span>class FileSessionStorage{ </span> <span>public function __construct(){ </span> <span>session_start(); </span> <span>} </span> <span>public function get( $key ){ </span> <span>return $_SESSION[$key]; </span> <span>} </span> <span>public function set( $key, $value ){ </span> <span>$_SESSION[$key] = $value; </span> <span>} </span><span>} </span> <span>class SimpleAuth{ </span> <span>protected $session; </span> <span>public function __construct(){ </span> <span>$this->session = new FileSessionStorage; </span> <span>} </span><span>} </span> <span>//creating a SimpleAuth </span><span>$auth = new SimpleAuth();</span>
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:
<span>class SimpleAuth{ </span> <span>protected $session; </span> <span>public function __construct( FileSessionStorage $session ){ </span> <span>$this->session = $session; </span> <span>} </span><span>}</span>
This way we can just implement and ask for an instance of the SessionStorage interface:
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>$this->connection = new Connection; </span> <span>} </span> <span>public function retrieveByCredentials( array $credentials ){ </span> <span>$user = $this->connection </span> <span>->where( 'email', $credentials['email']) </span> <span>->where( 'password', $credentials['password']) </span> <span>->first(); </span> <span>return $user; </span> <span>} </span><span>}</span>
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.
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span> <span>...</span>
The container is trying to instantiate the interface. We can fix that by creating a specific binding for our interface.
<span>class UserProvider{ </span> <span>protected $connection; </span> <span>public function __construct(){ </span> <span>... </span> <span>} </span> <span>public function setConnection( Connection $con ){ </span> <span>$this->connection = $con; </span> <span>} </span> <span>...</span>
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.
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!
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The above is the detailed content of Dependency Injection with Laravel's IoC. For more information, please follow other related articles on the PHP Chinese website!