Lesson 4
Dependency Injection in Symfony
Introduction to Dependency Injection

Hi there! Today we're going to learn about Dependency Injection (DI) and why it's important in Symfony. By the end of this lesson, you'll know what Dependency Injection is, why it's useful, how to implement it in your Symfony applications, and understand the relationship between the concept of Inversion of Control (IoC) and Dependency Injection.

What You Will Learn

In this lesson, we will:

  1. Understand what Dependency Injection is.
  2. Learn why Dependency Injection is important in making your code more maintainable and flexible.
  3. See how to configure and use DI in Symfony.
  4. Explore a more advanced example applying DI to a Symfony service.
  5. Discuss the benefits of using Dependency Injection and the concept of IoC.

Ready to dive in? Let's get started!

What is Dependency Injection?

Dependency Injection (DI) is a design pattern that allows an object to receive its dependencies from an external source rather than creating them itself. In simpler terms, instead of an object being responsible for getting the things it needs to work, we give those things to the object when we create it.

Dependency Injection brings several benefits:

  • Better Code Organization: Different parts of your application are separated, making the code easier to manage.
  • Easier Testing: By injecting dependencies, you can easily replace them with mock objects during testing.
  • Improved Flexibility: Making changes to one part of the application is easier because dependencies are managed externally.
Inversion of Control (IoC)

These benefits lead us to another important concept: Inversion of Control (IoC). IoC refers to the design principle where the control of object creation and dependency management is transferred from the object itself to an external entity (like a framework or container).

Imagine IoC as a broad concept, where the main idea is that objects do not create their dependencies or manage their life cycles. Instead, something else (for example, a framework) takes over that responsibility.

Dependency Injection is a specific method to achieve IoC. To put it simply:

  • IoC: A general idea of delegating control over dependencies.
  • DI: A form of IoC, where dependencies are injected into objects by an external source.

So, while all Dependency Injection is a form of Inversion of Control, not all Inversion of Control is achieved through Dependency Injection.

Implementing Dependency Injection: Defining the Ingredients

Now, let's see how to use Dependency Injection in Symfony with an advanced example.

We will create a sandwich using Bread and Cheese. First, define an IngredientInterface to ensure that both ingredients have a getName() method.

php
1<?php 2 3namespace App\Common; 4 5interface IngredientInterface 6{ 7 public function getName(): string; 8}

Next, create the Bread and Cheese classes that implement this interface.

php
1<?php 2 3namespace App\Common; 4 5class Bread implements IngredientInterface 6{ 7 public function getName(): string 8 { 9 return 'Slice of Bread'; 10 } 11}
php
1<?php 2 3namespace App\Common; 4 5class Cheese implements IngredientInterface 6{ 7 public function getName(): string 8 { 9 return 'Cheese'; 10 } 11}
Implementing Dependency Injection: Creating the Service

Here's the ExampleService that uses Dependency Injection to create a sandwich using Bread and Cheese.

php
1<?php 2 3namespace App\Service; 4 5use App\Common\Bread; 6use App\Common\Cheese; 7 8class ExampleService 9{ 10 private $bread; 11 private $cheese; 12 13 public function __construct(Bread $bread, Cheese $cheese) 14 { 15 $this->bread = $bread; 16 $this->cheese = $cheese; 17 } 18 19 public function makeSandwich(): string 20 { 21 return 'Sandwich with ' . $this->bread->getName() . ' and ' . $this->cheese->getName(); 22 } 23}

In the ExampleService class, Dependency Injection is applied through its constructor. Instead of ExampleService creating its own Bread and Cheese objects, these dependencies are provided via the constructor. This means that Symfony’s dependency injection container supplies the Bread and Cheese instances when an ExampleService object is created. This approach increases the flexibility of the service as it allows swapping Bread and Cheese with other implementations of IngredientInterface (e.g., Turkey, Lettuce, etc.) without modifying the ExampleService class.

Configuring Services in Symfony

Define the services in the services.yaml file.

YAML
1# config/services.yaml 2services: 3 App\Common\Bread: ~ 4 App\Common\Cheese: ~ 5 App\Service\ExampleService: 6 arguments: 7 $bread: '@App\Common\Bread' 8 $cheese: '@App\Common\Cheese'

Here, we're registering Bread and Cheese and injecting them into ExampleService. The @App\Common\Bread and @App\Common\Cheese syntax tells Symfony's service container to inject these services into the ExampleService.

Using the ExampleService in the Controller

As we learned in the past unit, to use the ExampleService in a controller, you inject it via the constructor.

php
1<?php 2 3namespace App\Controller; 4 5use App\Service\ExampleService; 6use Symfony\Component\HttpFoundation\JsonResponse; 7use Symfony\Component\Routing\Annotation\Route; 8 9class ExampleController 10{ 11 private $exampleService; 12 13 public function __construct(ExampleService $exampleService) 14 { 15 $this->exampleService = $exampleService; 16 } 17 18 /** 19 * @Route("/example/sandwich", name="example_sandwich", methods={"GET"}) 20 */ 21 public function makeSandwich(): JsonResponse 22 { 23 return new JsonResponse($this->exampleService->makeSandwich()); 24 } 25}

In the ExampleController class, Dependency Injection is again applied through its constructor. Instead of the controller creating the ExampleService object itself, it receives it as a dependency injected by the Symfony framework. This is done via the constructor: public function __construct(ExampleService $exampleService). By doing this, the controller can focus on handling HTTP requests, while the business logic for creating sandwiches is managed by the ExampleService. This separation of concerns makes the code easier to maintain and test.

Benefits of Dependency Injection

Let's review the main advantages of using Dependency Injection:

  1. Improved Modularity: Dependencies (services) are separate from the classes that use them.
  2. Ease of Testing: You can easily swap out dependencies with mock objects for testing.
  3. Scalability: Adding new features or modifying existing ones is easier because dependencies are managed outside of the class.

By following the IoC principle, Dependency Injection allows the control of object creation to be handled by Symfony, leading to more maintainable, flexible, and testable code.

Summary

Today we learned:

  1. What Dependency Injection is: We saw how DI allows objects to receive their dependencies from an external source.
  2. Why Dependency Injection is Important: Using DI improves code organization, makes testing easier, and increases application flexibility.
  3. How to Implement DI in Symfony: We configured services, injected them into controllers, and used them effectively.
  4. A More Advanced Example: We created and used an ExampleService to make sandwiches.
  5. Inversion of Control (IoC): We understood how DI implements the IoC principle to improve code architecture.

Congratulations! You now have a solid understanding of Dependency Injection and its importance in Symfony applications. This knowledge will help you create better-organized, more maintainable, and flexible applications.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.