PHP

Service Container & DI “sành điệu” với Laravel

Hi, long time no see 😀

Laravel là một framework nổi tiếng nhất trong cộng đồng PHP, 1 phần là vì cấu trúc tuyệt vời của nó. Khi nói đến architectural concepts của Laravel, không thể không nhắc tới Service Container. Khi bạn đụng đến 1 bài toán lớn hơn, cần sử dụng nhiều services, dependencies… thì bạn sẽ cần hiểu và sử dụng được Service Container.

Về Dependency Injection thì mình sẽ có 1 bài post khác cụ thể về nó hơn, nhưng cũng khá đơn giản, tạm hiểu là thay vì new một object, ta sẽ khởi tạo nó ở hàm __construct() của class sử dụng mà không cần new object, vì thế ta sẽ không cần quan tâm đến chuyện initiate object đó như thế nào.

Okay let’s go!

Xét đoạn code thường xuyên gặp sau:

class RedirectionController extends Core
{
    public function redirect(Request $request)
    {
        $params = $request->all();
        $user = $request->user();
        ...
    }
}

Bạn đã bao giờ thắc mắc tại sao Laravel có thể trả về đúng request, user data hiện tại mà không cần bất cứ 1 input nào chưa? Chỉ cần 1 thao tác type hint đơn giản là đủ?

Thực ra phép màu ở đây nằm ở Service Container của Laravel. Để hiểu về Service Container, chúng ta sẽ phải tìm hiểu khái niệm về Binding.

Binding tức là hành động đăng ký 1 Service với Service Providers. Sau khi đã đăng ký xong, thì ta có thể sử dụng Service đó under app container scope. Có nhiều cách để Bind, các đơn giản nhất như sau:

$this->bind('HelpSpotAPI', function ($app) {
    return new HelpSpotAPI($app->make('HttpClient'));
});

Như ta thấy, bind() nhận 2 parameter chính đó là: Tên class interface mà ta muốn bind, quan trọng hơn là closure function sẽ init và return một instance của class mà ta muốn trả về. Cách truy xuất & sử dụng instance trả về này như thế nào thì lát nữa mình sẽ bàn sau.

Singleton Binding

Khi sử dụng singleton binding, instance trả về sẽ được khởi tạo 1 lần duy nhất, sau đó cùng một instance đó sẽ được sử dụng lại ở các lần gọi tiếp theo. Tùy theo yêu cầu bài toán mà chúng ta chọn 1 cách binding hợp lý.

$this->singleton('HelpSpotAPI', function ($app) {
    return new HelpSpotAPI($app->make('HttpClient'));
});

Binding Interface với Class nào implement nó

Đây là một chức năng làm nên sức mạnh của Laravel. Example cho dễ hiểu là giả sử ta có class S3Storage implement Storage interface, và đã thực hiện Binding:

<?php
use Illuminate\Support\ServiceProvider;

class StorageServiceProvider extends ServiceProvider { 
    public function register()
    {
        $this->bind('StorageInterface', 'S3Storage');
    }
}

Sử Dụng (Automatic Injection):

Sau khi đã khai báo với Service Provider, ta có thể sử dụng nhiều cách khác nhau để resolve và call tới instance mong muốn, nhưng một cách mà ta sẽ thường xuyên sử dụng đó là Automatic Injection.

Quay lại với tình huống giả sử bên trên, sau khi đã Binding, ở controller nếu cần sử dụng S3Storage class, ta chỉ cần type-hint Storage interface là được:

<?php
class FileController extends Controller {
    private $storage; 

    // Laravel will automatically resolve StorageInterface to the S3Storage class
    public function __construct(StorageInterface $storage) 
    { 
        $this->storage = $storage;
    }
    
    public function deleteFile()
    {
        $this->storage->delete();
    }
}

Laravel container sẽ tự lựa chọn đúng class mà ta đã binded. Làm như vậy, cấu trúc code sẽ dễ dàng module hoá hơn, nếu thay storage class khác, ta chỉ cần định nghĩa thêm 1 class mới, và thay nó cho S3Storage ở giai đoạn Binding là xong.

Lời cuối

Vậy là chúng ta đã hiểu cơ bản về Service Container của Laravel, một concept rất quan trọng để có thể cấu trúc được những hệ thống phức tạp hơn. Việc tiếp theo là các bạn hãy thử tự tìm hiểu xem Laravel xử lý binding Request…. như thế nào nhé 😉
Have fun & Thanks for reading!

 


References:

Reply