Dependency Injection hoạt động thế nào trong Laravel?

Giới thiệu Dependency Injection

Dependency Injection là gì? Cách sử dụng thế nào? Tác dụng nó ra sao? Nó hay như thế nào?

Mời các bạn đón đọc tại: Tìm hiểu về Service container

Giới thiệu Reflection

Khi tôi hỏi một bạn rằng tại sao Laravel có thể "Dependency Injection" được? Bạn ấy bảo vì mình đã "bind" vào Service Container ở Provider rồi nên giờ mình đưa vào Parameter thì nó sẽ truyền vào cho mình. Tôi "cười nhẹ" rồi hỏi tiếp: ừm, đó là em vừa mô tả cách để mình sử dụng thôi, anh muốn hỏi là vì sao Service Container biết là mình đang cần cái gì ấy?!! Nghe xong bạn ấy nhìn những dòng code đầy rẫy những Dependency Injection mà chẳng biết nói gì, có lẽ bạn ấy muốn nói rằng: đây, anh nhìn đây mà chẳng lẽ còn không biết em đang cần gì sao? Thế rồi tôi gửi link bài viết này cho bạn ấy đọc. Ahihi.

Reflection là gì? Đây là nguyên văn giới thiệu về Reflection của php.net nhé:

PHP 5 comes with a complete reflection API that adds the ability to reverse-engineer classes, interfaces, functions, methods and extensions. Additionally, the reflection API offers ways to retrieve doc comments for functions, classes and methods.

Dịch ra chắc bạn biết nó là gì rồi nhỉ. Với Reflection, các bạn có thể khảo sát class, interface, function, method và cả extension xem nó tên gì, cần gì, .... Từng đấy chưa hết, nó còn có thể lấy được comment của function, class, method. Bây giờ bạn thấy giá trị của comment rồi chứ, và cũng phải có quy tắc viết comment nữa đúng không?!!

Cụ thể Reflection có những khả năng nào? Cách sử dụng ra sao? Mời các bạn đón đọc tại: PHP Reflection

Super Micro Container Example

Mọi lý thuyết chúng ta đã rõ, vậy bạn đã hiểu vì sao Service Container của Laravel có thể biết bạn muốn cái gì chưa? Chúng ta sẽ làm một Container siêu siêu nhỏ nhé.

Trước tiên chúng ta chuẩn bị đối tượng sẽ sử dụng cho Dependency Injection, chúng ta chọn cái mà ta vẫn dùng mỗi ngày - computer.

interface Computer
{
    public function run();
}

class Desktop implements Computer
{
    public function run()
    {
        echo 'I am a Desktop Computer';
    }
}

class Laptop implements Computer
{
    public function run()
    {
        echo 'I am a Laptop Computer';
    }
}

Xong đối tượng để Dependency Injection, chúng ta chuẩn bị đối tượng sẽ cần Dependency Injection. Object nào được nhỉ? À, it's me.

class Developer
{
    protected $computer;

    public function __construct(Computer $computer)
    {
        $this->computer = $computer;
    }

    public function work()
    {
        $this->computer->run();
    }
}

Đến đây bạn đã thấy giống như những gì bạn thường dùng ở Laravel chưa?

  • Computer chính là các Interface bạn thường Inject ở các paramter, VD Request, Repository, ...
  • Laptop, Desktop chính là các class của các đối tượng sẽ được truyền vào.
  • Developer chính là Controller và method work là action.

Yeah, mọi thứ đã có, bây giờ đến phần Service Container.

Lưu ý: đây là đoạn code minh họa mô tả hoạt động của Service Container để các bạn thấy Service Container xử lý Dependency Injection thế nào, không xử lý ngoại lệ và không liên quan đến Container của Laravel.


// Binding ở Service Provider
$binding = [
    'Computer' => 'Laptop', // Khi cần Computer thì trả về Laptop
];

// Sau một công đoạn phức tạp để phân tách URL, Laravel đã tìm được Controller và Action cần thực thi như đây:
$controllerName = 'Developer';
$actionName = 'work';

$reflectorController = new ReflectionClass($controllerName);
$constructorParameters = $reflectorController->getConstructor()->getParameters();

$params = []; // Parameters sẽ truyền vào constructor

foreach ($constructorParameters as $parameter) {
    $paramClass = $parameter->getClass(); // Với trường hợp này nó sẽ là Computer, nếu không phải Interface/Class name thì nó sẽ là null
    $paramValue = null; // default
    if (!is_null($paramClass) && isset($binding[$paramClass])) {
        // Đến đây, với Laravel thì nó sẽ đệ quy để tìm ra đối tượng của $binding[$paramClass]
        // Chúng ta học thì chúng ta làm đơn giản thôi.
        $paramValue = new $binding[$paramClass](); // Đơn giản ghê! Ahihi.
    }
    $params[] = $paramValue;
}

$controller = $reflectorController->newInstanceArgs($params); // Khởi tạo đối tượng. Tương đương với: $controller = new Developer(new Laptop)

$controller->$actionName(); // Gọi action, tương đương với $controller->work()

Liên kết

Nói có sách, mách có chứng. Liệu rằng Laravel có dùng Reflection để xử lý Dependency Injection không? Các bạn có thể xem tại đây: https://github.com/laravel/framework/blob/5.4/src/Illuminate/Container/Container.php#L710

Trích code:

    // Other code.....
    public function build($concrete)
    {
        // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.
        if ($concrete instanceof Closure) {
            return $concrete($this);
        }

        $reflector = new ReflectionClass($concrete);

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface of Abstract Class and there is
        // no binding registered for the abstractions so we need to bail out.
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        $constructor = $reflector->getConstructor();

        // If there are no constructors, that means there are no dependencies then
        // we can just resolve the instances of the objects right away, without
        // resolving any other types or dependencies out of these containers.
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        $dependencies = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        $instances = $this->resolveDependencies(
            $dependencies
        );

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }
    // Other code....

Chưa biết thế nào nhưng có từ reflector thì chắc là đúng rồi nhỉ?

Lời cuối: chúc các bạn code khỏe, code đẹp!


All Rights Reserved