Understanding Dependency Injection in Laravel

Vitaliy Dotsenko
Legacybeta
Published in
4 min readMay 7, 2020
Original Photo by 五玄土 ORIENTO

Dependency Injection (DI) — possesses a powerful and at the same time simple to use technique. The main goal of this technique is making a class independent of its dependencies by creating depending objects outside of the class and pass the link to those objects in the constructor, setter method, or property of the class where those objects should be used.

The advantage of using dependency injection is — it’s an easy way to swap the class with another one and create mocks for unit testing.

For example, dependency injection in the class constructor or in other words is “constructor injection”:

class User
{
protected $userService;

public function __construct(UserService $userService)
{
$this->userService = $userService
}
}

The UML diagram of this relation is:

Dependency injection in the constructor

Dependency Injection uses with the adjacent principle — Dependency Inversion Principle.

Dependency Inversion Principle (DIP) — means use flexible abstractions like Interfaces instead of the “real” classes. It’s derived from the fifth principle of the object-oriented programming principles SOLID.

The principle states two things:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
  • Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

Above example but using DIP principle will be:

class User
{
protected $userService;

public function __construct(UserServiceInterface $userService)
{
$this->userService = $userService
}
}

And the UserServiceInterface interface definition:

interface UserServiceInterface
{
public function method();
}

The class which implements the above interface:

class UserService implements UserServiceInterface
{
public function method()
{
//
}
}

The UML diagram of this relation is:

Use Dependency Inversion Principle

Dependency Injection in Laravel

Laravel has the Service Container which helps manage the class dependencies in an application. Laravel provides automatic injection using “type hinting” in the constructors of controllers, middlewares, event listeners that are resolved by container. It uses PHP Reflection to automatically resolve dependencies.

You can get the Service Container instance by calling the global Laravel function app():

$container = app();

Further, you can use Laravel Service Container outside of Laravel just install by composer:

composer require illuminate/containeruse Illuminate\Container\Container;$container = Container::getInstance();

Binding

In Laravel registration of the class binding located in Service Providers, it’s a general place for registering Laravel stuff.

To register a new binding you can use the existing register() method in the existing service provider app/Providers/AppServiceProvider.php or create a new service provider by the artisan command php artisan make:provider SomeServiceProvider.

For example, to bind the UserService class to the UserServiceInterface interface (can be used with an abstract class too) using the existing service provider AppServiceProvider:

<?phpnamespace App\Providers;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind('UserServiceInterface', function ($app) {
return new UserService(SOME_SERVICE_CONFIG_VARIABLES);
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
}

or just define without new if not needed to pass additional arguments into the constructor:

$this->app->bind('UserServiceInterface', UserService::class);

It’s easy to assign to it an alias:

$this->app->alias('UserServiceInterface', 'userService');

Also, it’s easy to use the class as Singleton for preventing creating new object each time when a class will be resolved:

$this->app->singleton('UserServiceInterface', UserService::class);

You can extend a class and return a different object but this class must implement the same interface otherwise it will return the exception “Call to undefined method”:

$this->app->extend(UserService::class, function ($userService) {
return new UserServiceWrapper($userService);
});

If you need grouping multiple bindings, you can use tags:

$this->app->tag(UserService::class, 'services');
$this->app->tag(PostService::class, 'services');

Resolving

To resolve a class instance in Laravel you can use the global function resolve():

$userService = resolve('UserServiceInterface');

or use the make() method from the global function app():

$userService = app()->make('UserServiceInterface');

or just use app():

$userService = app('UserServiceInterface');

To retrieve all tagged (grouped) instances use the tagged method with the tag name as a parameter:

$services = app()->tagged('services')

Conclusion

Dependency Injection and Dependency Inversion principle make weak relations between classes which in turn make extending the application and unit testing easier.

I hope after reading this article you have a better understanding of how Service Container works and how it manages DI dependencies in Laravel.

--

--

Vitaliy Dotsenko
Legacybeta

I like coding, open-source software and hi-tech 🚀