Dependency Injection with Laravel

Dependency Injection – With Laravel

Dependency injection is a commonly used design pattern in object oriented programming. Through some pre-established conventions, we are able to manage the creation of our dependencies more easily. We can declare, replace, or even mock the dependencies as needed without the need to change the code that relies on the dependency.

For example, let’s say we have some authentication logic

We want to write a test for any code that interacts with this logic, you can see that we would need to seed a lot of data upfront. For any authenticated controller test, we would need to seed a client, a user, some permissions, and then generate an auth token for the above. Using dependency injection, we can avoid all this setup and just mock authenticate to always return true or false. Let’s take a look at how to do so in Laravel!

How to perform Dependency Injection

Like most frameworks, Laravel allows us to use dependency injection to organize our code. Let’s take a look at how to do it! These are the steps that we will need to perform.

  1. Define the dependency
  2. Bind the dependency
  3. Inject the dependency
  4. (Optional) Mock/Replace the dependency

Define the Dependency

First, you need to define a class of objects that other classes will depend on. Here, we will create a simple HelperService class that we want to use in other parts of our code.

 

Bind the Dependency

Now, we need to construct the dependency and bind it to the Laravel service container. In Laravel, this is usually done by creating a service provider and registering it.

In the generated file, you would register your binding and the function that should be executed whenever that binding is needed. This is where we would construct the class.

Finally, you would bind the HelperServiceProvider to the Laravel service container in config/app.php

Inject the Dependency

Now that the dependency binding is declared to the Laravel service container, we can inject it into our Laravel code wherever it is needed. For instance, your can use it in your controllers or middleware through automatic injection. Laravel will detect that you want a HelperService class which it can match to the binding you declared earlier.

 

We could also manually inject that dependency into any other class or function directly using the Laravel App facade.

 

You can see that we can now make use of the new HelperService() invoked in the service provider in all the subsequent code without explicitly constructing it. The construction of the class is controlled by the Laravel startup process.

Mock/Replace the Dependency

Now that we are using dependency injection to manage the dependency objects, we have the ability to mock them and replace them during runtime. This is particularly useful during tests (e.g. mocking your authentication to always return true).

You can read more about binding objects to the service container here: https://laravel.com/docs/9.x/container

 

Now, instead of instantiating the actual HelperService in our tests, we can use a mock of that object instead! This is great for isolating parts of your code to perform your unit tests more easily.

You can find more about mocking here: https://laravel.com/docs/9.x/mocking#mocking-objects

When to use Dependency Injection

Dependency injection is a great tool for organizing our code, but like any other tool, we should only use it when appropriate. Personally, I find that dependency injection is most useful for the following 2 purposes:

  1. We need to maintain and modify some state throughout the request lifecycle (e.g. a singleton that is used throughout middlewares, controllers, and service classes). This is somewhat similar to why I would put something in Redux, when passing some object down through many layers of components/services becomes unwieldy.

  2. We want some logic or state that we may want to abstract away and make it mockable (e.g. http clients, auth state)

A great example is authenticate state. Usually, you may need to perform many steps before you can create an authenticated request. This could be too complicated and make your tests too coupled with your authentication logic. Do you really need to re-test how to generate an authentication token in every API request test that you create?

Using dependency injection, we could instead just seed the bare minimum and mock the authentication layer. The resulting testing code could look like below:

 

Conclusion

Now you know why, when, and how to use Dependency Injection in Laravel. This technique can also be applied to other frameworks, Spring Boot in Java, Ruby on Rails, Django, and most other web frameworks all have ways to implement this technique. Just remember to use it when appropriate! Don’t go overboard with your mocking! Testing with the real thing is always needed at some point!

おすすめ記事