Practical PHP Dependency Injection Course – Understanding and Application
Introduction to Dependency Injection
Dependency Injection (DI) is a programming technique that involves passing required dependencies to objects instead of creating them within the object. It is one of the most important design patterns in object-oriented programming, allowing for increased flexibility and ease of code testing. Thanks to dependency injection, code becomes more modular, facilitating its development and maintenance. In practice, this means that objects are not responsible for creating their dependencies, leading to looser coupling between classes and better code organization.
What is Dependency Injection?
Dependency Injection (DI) is a design pattern that allows for loose coupling between classes, eliminating the need to manually create dependencies within objects. This makes code modular, easier to test, and more flexible.
In the traditional approach, a class creates its own dependencies. A good example is a database handling class:
public function __construct() {
$this->database = new DatabaseConnection();
}
However, such construction makes changing the behavior of DatabaseConnection require editing UserService, which violates the dependency inversion principle (D in SOLID). By using a container, all necessary dependencies will be automatically found, regardless of whether we use an interface or class name as the key in the container. Moreover, DI can reduce the number of new connections created each time an email is sent, increasing efficiency and resource savings in web applications.
readonly class UserService
{
public function __construct(private DatabaseConnection $database) {}
}
How does Dependency Injection work in PHP?
In my PHP course, you learn how to professionally manage dependencies in an application. Dependency Injection (DI) is a key pattern that allows for cleaner and more modular code. Unlike the classic approach where objects create their dependencies, here the DI container takes control, dynamically providing class instances where they are needed.
In my solution, I use a Container that works like an intelligent service registry, storing objects in dot notation structure (DotContainer). This allows you to conveniently manage configuration, e.g., setting database.host or mailer.smtp, and then retrieving these values anywhere in the application. This makes code more readable and flexible.
The biggest advantage of DI in my PHP course is Autowire, a system that independently analyzes classes and automatically resolves their dependencies. Thanks to the Reflection API, the DI container checks the class constructor and provides appropriate objects – without the need to manually define them. As a result, your application is not only better organized but also ready for scaling and easy to test.
If you want to learn modern Dependency Injection techniques, learn to use PSR-11, DI containers, and autowiring, check out my PHP course\! Thanks to practical examples, you will quickly master professional dependency management, allowing you to create clean and efficient code.
What is dot notation and how does it work?
In my PHP course, you learn about dot notation, which is a convenient way to organize and retrieve data in an application. Instead of using multidimensional arrays and referring to values in nested structure ($config["database"]["host"])
, you can write and read data as a single string:
$config->set("database.host", "localhost");
echo $config->get("database.host"); // localhost
In my DI container, I used dot notation in DotContainer, allowing you to organize dependencies simply and dynamically read them. This solution makes code in your application more modular and flexible, similar to large frameworks like Laravel.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Base;
use ArrayObject;
class DotContainer extends ArrayObject
{
private readonly ArrayGet $arrayGet;
private readonly ArraySet $arraySet;
/**
* @param array<int|string, mixed> $array
*/
public function __construct(array $array = [])
{
parent::__construct($array);
$this->arrayGet = new ArrayGet($this);
$this->arraySet = new ArraySet($this);
}
public function get(string $key, mixed $default = null): mixed
{
return $this->arrayGet->get($key, $default);
}
public function set(string $key, mixed $default = null): void
{
$this->arraySet->set($key, $default);
}
public function has(string $key): bool
{
return $this->arrayGet->get($key) \!== null;
}
}
What benefits does Autowire provide and how does bind work in the DI container?
In my PHP course, you learn how Autowire automatically injects dependencies without the need to manually define them in code. By using the Reflection API, the DI container analyzes the class constructor and provides appropriate instances, making code cleaner, more scalable, and easier to maintain.
Better code organization and fewer class dependencies – SOLID principles in practice
Dependency Injection is not just convenience but also compliance with SOLID principles, which are the foundation of well-designed code. Using setters, you can easily configure dependencies. In my PHP course, you learn how to apply SOLID in combination with a DI container, making your applications modular, easy to test, and ready for scaling.
Practical Examples of Dependency Injection Usage
Dependency injection is often used with injection engineering, which involves passing dependencies in the application structure. An example of dependency injection is passing an S3 client instance to a class that uses it. Instead of creating an S3 client instance inside the class, we can pass it as a dependency. Thanks to this approach, the FileUploader class is not directly dependent on a specific S3 client implementation, allowing for easier testing and code modification.
class FileUploader
{
public function __construct(private readonly S3ClientContract $s3Client) { }
public function upload(string $filePath): bool
{
$this->client->upload($filePath);
return true;
}
}
Want to see more practical examples? Download free ebook sample 📥 or Buy PHP course 🎓
Design Patterns Supporting Dependency Injection
Design patterns play a key role in facilitating and optimizing the use of Dependency Injection in programming projects. One of the most significant patterns is "Abstract Factory", which allows creating families of related objects without specifying their concrete classes. The "Builder" pattern enables constructing complex objects step by step, which is particularly useful for objects with multiple dependencies. "Singleton", though controversial in the context of DI, can be used to manage unique object instances, ensuring that only one instance of a given object exists throughout the application.
How does Dependency Injection Container help in running unit tests?
In my PHP course, you learn how Dependency Injection Container (DI Container) facilitates writing unit tests. The key problem in testing is class dependence on concrete implementations – DI eliminates this problem by allowing easy replacement of objects with mocks.
Without Dependency Injection: Each class independently creates its dependencies, making testing difficult - the constructor of this class must manually create the entire database.
class UserService {
private DatabaseConnection $db;
public function __construct()
{
$this->db = new DatabaseConnection(); // Testing requires a real database\!
}
}
With Dependency Injection:
In the test below, we see a dependency injection container for unit tests for MySQL database. Using the $this->createMock
function allows executing tests without physically running the database.
<?php
namespace Tests\DBAL\Connection;
use DJWeb\Framework\Base\Application;
use DJWeb\Framework\Config\Contracts\ConfigContract;
use DJWeb\Framework\DBAL\Connection\MySqlConnection;
use PDO;
use PDOException;
use PDOStatement;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\BaseTestCase;
class MySqlConnectionTest extends BaseTestCase
{
private MySqlConnection|MockObject $connection;
private $applicationMock;
public function testConnect(): void
{
$this->mockConfig();
$pdoMock = $this->createMock(PDO::class);
$this->connection = $this->getMockBuilder(MySqlConnection::class)
->onlyMethods(["connectMysql"])
->getMock();
$this->connection->expects($this->once())
->method("connectMysql")
->willReturn($pdoMock);
$this->connection->connect();
$pdo = $this->connection->getConnection();
// Call connect again to ensure it doesn"t try to reconnect
// and return the same instance of PDO
$this->connection->connect();
$pdo2 = $this->connection->getConnection();
$this->assertSame($pdo, $pdo2);
}
}
PSR and Dependency Injection
PSR (PHP Standards Recommendations) standards are important for PHP programmers because they facilitate creating code that is more readable, easier to maintain and test. Dependency injection is one of the most important aspects of PSR because it allows for increased flexibility and ease of code testing. The PSR-11 standard defines the dependency container interface in PHP, which is crucial for dependency injection. Thanks to PSR-11, programmers can create DI containers that comply with best practices and are easy to integrate with various frameworks and libraries.