Ý tưởng của Container

Container cần làm 3 việc chính

  1. Nhận class cần tạo
  2. Phân tích constructor để tìm dependency
  3. Resolve dependency và tạo object

Chi tiết hơn

  1. Nhận lớp cần khởi tạo
    Hệ thống nhận vào tên lớp mà chương trình cần tạo đối tượng (instance).
  2. Phân tích hàm khởi tạo để tìm các phụ thuộc
    Sử dụng Reflection để đọc constructor của lớp đó và xác định các lớp hoặc đối tượng mà nó phụ thuộc.
  3. Giải quyết các phụ thuộc và tạo đối tượng
    Container sẽ tạo các đối tượng phụ thuộc trước, sau đó truyền chúng vào constructor để khởi tạo đối tượng của lớp ban đầu.

Nói ngắn gọn hơn theo đúng thuật ngữ IoC:

Nhận lớp → phân tích constructor → giải quyết phụ thuộc → tạo đối tượng.

Flow thực tế của Laravel cũng gần như vậy

resolve(Class)

↓
Reflection đọc constructor

↓
resolve các dependency

↓
newInstanceArgs()

Code IoC Container mini

class Container
{
    public function resolve($class)
    {
        $reflector = new ReflectionClass($class);

        if (!$reflector->isInstantiable()) {
            throw new Exception("Class {$class} không thể khởi tạo");
        }

        $constructor = $reflector->getConstructor();

        if (is_null($constructor)) {
            return new $class;
        }

        $parameters = $constructor->getParameters();

        $dependencies = $this->resolveDependencies($parameters);

        return $reflector->newInstanceArgs($dependencies);
    }

    protected function resolveDependencies($parameters)
    {
        $dependencies = [];

        foreach ($parameters as $parameter) {

            $type = $parameter->getType();

            if ($type && !$type->isBuiltin()) {

                $dependencies[] = $this->resolve($type->getName());

            } else {

                throw new Exception("Không resolve được dependency");
            }
        }

        return $dependencies;
    }
}

Ví dụ dependency

Ta tạo 3 class giống hệ thống thật.

class Logger
{
}

class PaymentService
{
    public function __construct(Logger $logger)
    {
    }
}

class OrderService
{
    public function __construct(PaymentService $payment)
    {
    }
}

Dependency graph

OrderService
   ↓
PaymentService
   ↓
Logger

Sử dụng Container

$container = new Container();

$orderService = $container->resolve(OrderService::class);

Container sẽ tự tạo toàn bộ dependency.

Thực tế tương đương

new OrderService(
    new PaymentService(
        new Logger()
    )
);

Nhưng ta không cần tự viết tay nữa.


Điều gì đang xảy ra bên trong

Bước 1 Container nhận class

resolve(OrderService)

Bước 2 Reflection đọc constructor

OrderService::__construct(PaymentService $payment)

Bước 3 Container resolve dependency

resolve(PaymentService)

Bước 4 PaymentService lại có dependency

PaymentService(Logger $logger)

Container tiếp tục

resolve(Logger)

Bước 5 Logger không có dependency

Container tạo luôn

new Logger()

Bước 6 Tạo toàn bộ object

Cuối cùng container gọi

$reflector->newInstanceArgs($dependencies);

Tức là

new OrderService(
    new PaymentService(
        new Logger()
    )
)

Đây chính là Auto Dependency Injection

Laravel làm y hệt nhưng thêm rất nhiều tính năng

Ví dụ

bind interface

PaymentInterface → StripePayment

singleton

chỉ tạo object 1 lần

instance

đăng ký object sẵn

service provider

đăng ký binding khi boot app

Điểm quan trọng nhất cần nhớ

Reflection không tự tiêm dependency

Reflection chỉ làm 2 việc

đọc constructor
tạo object

IoC Container mới là thứ

phân tích dependency
resolve dependency
quản lý lifecycle object