Dependency Injection và Container trong PHP

Tạo bởi Hoàng Vũ, chỉnh sửa cuối lúc 7 tháng 5, 2025

Trong bài học này, bạn sẽ tìm hiểu về Dependency Injection (DI) – một kỹ thuật lập trình giúp mã nguồn tách rời các thành phần phụ thuộc vào nhau, từ đó dễ kiểm thử và mở rộng. Đồng thời, bạn sẽ được giới thiệu Service Container, một công cụ quản lý và cung cấp các dependency một cách tự động và linh hoạt. Đây là một kỹ năng cốt lõi để xây dựng ứng dụng PHP hiện đại, đặc biệt là khi làm việc với framework hoặc viết kiến trúc module hóa.

1. Dependency là gì?

Trong lập trình, một dependency (sự phụ thuộc) là một đối tượng mà một lớp cần sử dụng để hoạt động.

Ví dụ:

class Logger {
    public function log($message) {
        echo "Log: $message";
    }
}

class UserService {
    private $logger;

    public function __construct() {
        $this->logger = new Logger(); // hardcoded dependency
    }

    public function register($username) {
        // Logic đăng ký
        $this->logger->log("User $username registered");
    }
}

Vấn đề ở đây: UserService phụ thuộc chặt vào lớp Logger. Nếu muốn thay Logger, sẽ phải sửa đổi mã gốc.

2. Dependency Injection là gì?

Dependency Injection (DI) là kỹ thuật đưa dependency vào từ bên ngoài thay vì khởi tạo bên trong lớp.

3 cách phổ biến để inject dependency:

  • Constructor Injection
  • Setter Injection
  • Method Injection

Ví dụ - Constructor Injection:

class UserService {
    private $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    public function register($username) {
        $this->logger->log("User $username registered");
    }
}

// Khởi tạo bên ngoài
$logger = new Logger();
$userService = new UserService($logger);
$userService->register("admin");

Ưu điểm:

  • Dễ kiểm thử (test được UserService với mock Logger)
  • Dễ thay thế Logger (ví dụ: FileLogger, DatabaseLogger…)

3. Service Container là gì?

Một Service Container là nơi chứa, cấu hình và cung cấp các dependency. Bạn định nghĩa các service (class) một lần, rồi gọi ở bất kỳ đâu.

Giống như một "hộp đựng các dependency", container giúp bạn không phải khởi tạo thủ công và quản lý vòng đời các đối tượng.

4. Tự xây dựng Service Container đơn giản

Bước 1: Tạo Container class:

class Container {
    protected $services = [];

    public function set($name, callable $resolver) {
        $this->services[$name] = $resolver;
    }

    public function get($name) {
        if (isset($this->services[$name])) {
            return $this->services[$name]($this);
        }

        throw new Exception("Service $name not found");
    }
}

Bước 2: Đăng ký và sử dụng:

$container = new Container();

$container->set('logger', function() {
    return new Logger();
});

$container->set('userService', function($c) {
    return new UserService($c->get('logger'));
});

$service = $container->get('userService');
$service->register("john");

Lợi ích:

  • Tự động quản lý dependency
  • Không cần "new" khắp nơi
  • Hỗ trợ lazy-loading (chỉ tạo khi cần)
  • Rất gần với cách framework Laravel, Symfony… hoạt động

5. Ứng dụng thực tế với cấu trúc tách lớp

Ví dụ tình huống thực tế: Bạn muốn sử dụng FileLogger thay vì Logger gốc.

class FileLogger extends Logger {
    public function log($message) {
        file_put_contents('log.txt', "Log: $message\n", FILE_APPEND);
    }
}

$container->set('logger', function() {
    return new FileLogger(); // thay thế dễ dàng
});

Bạn không cần sửa UserService hay controller — mọi thay đổi được thực hiện tại Container.

Kết luận

Bài học này đã giúp bạn hiểu và áp dụng:

  • Dependency Injection để làm cho ứng dụng dễ kiểm thử, mở rộng, và linh hoạt hơn.
  • Service Container như một công cụ trung tâm để quản lý dependency một cách hiệu quả.

Đây là nền tảng quan trọng cho các kiến trúc chuyên nghiệp, đặc biệt khi bạn phát triển ứng dụng lớn hoặc sử dụng framework hiện đại như Laravel, Symfony, Slim, v.v.

Bài tập thực hành

  1. Viết một class Mailer và inject vào UserService để gửi email khi đăng ký.
  2. Tạo một Service Container đơn giản để quản lý Logger, Mailer, và UserService.
  3. Thay thế Logger bằng một class khác (ví dụ DatabaseLogger) thông qua container mà không sửa mã UserService.
Website Logo

Với hơn 10 năm kinh nghiệm lập trình web và từng làm việc với nhiều framework, ngôn ngữ như PHP, JavaScript, React, jQuery, CSS, HTML, CakePHP, Laravel..., tôi hy vọng những kiến thức được chia sẻ tại đây sẽ hữu ích và thiết thực cho các bạn.

Bình luận

Website Logo

Chào, tôi là Vũ. Đây là blog hướng dẫn lập trình của tôi.

Liên hệ công việc qua email dưới đây.

lhvuctu@gmail.com

Chúng Tôi Trên

Bạn đang muốn học về lập trình website?

Bạn cần nâng cao kiến thức chuyên nghiệp hơn để nâng cao cơ hội nghề nghiệp? Liên hệ