Service Container và các cách sử dụng(Laravel)

trieu.dev.da

Nguyễn Thanh Triều
1. Auto injection
Ví dụ ta có một sự phụ thuộc class rườm rà như thế này. Class A phụ thuộc vào Class B, Class B lại phụ thuộc vào Class C, Class C lại phụ thuộc vào class D. Nếu muốn khởi tạo 1 instance của class A ta cần viết như thế này:
1681531086564.png

Khi làm thực tế không ít lần chúng ta gặp phải trường hợp tương tự, nhìn thấy rắc rối phải không nào 😂😂😂 Nhưng nếu chúng ta dùng Service Container của Laravel thì chúng ta chỉ cần viết thế này
1681531092070.png

Đơn giản hơn rất nhiều đúng ko. Chúng ta còn chẳng cần quan tâm đến Class A phụ thuộc vào Class Cha nào rồi Class Cha này có phụ thuộc vào Class Ông nào ko, ..... Service Container đã lo cho chúng ta hết. Chúng ta gọi việc này là Auto Injection. Tức là Service Container tự nó đi tìm những class phụ thuộc vào class được bind vào Service Container và tự injection vào. Vậy làm sao Service Container lại làm được điều này, đó chính là nhờ vào Php Reflection , tức là class sẵn có này của Php cung cấp khả năng phân tích cấu trúc bên trong của 1 class. Từ đó Service Container biết để khởi tạo instance của Class A thì cần Class B. Service Container lại tiếp tục phân tích để khi tạo instance của Class B thì cần Class C, ... cứ tiếp tục đệ quy như thế này cho đến hết.

2. Cung cấp đa dạng các cách binding
Cấu trúc chung của binding
1681531100788.png

Giải thích $abstract: định dạng string. Là tên của service mà mình đặt trên khi binding vào Service Container $concrete: định dạng Closure|string|null. Đại diện cho class muốn binding vào Nếu là Closure thì sẽ là 1 1 function Closure, class returen trong function Closure này chính là Class dc binding vào. Đây là 1 cách viết tùy biến cao nhất so với định dạng string|null Nếu là string thì chính là tên của Class muốn binding vào Nếu là null thì sẽ lấy $concrete = $abstract $shared: định dạng kiểu boolean, giá trị mặc định là false. Nó sẽ liên quan tới cách binding singleton sẽ nói ở mục sau. Các loại binding

2.1 Binding Basics
Binding simple
Binding tronmg Service Providers
1681531116737.png

$this->app ở đây chính là Service Container được tạo ra khi khởi động ứng dụng Laravel Các này sẽ tương đương với cách ta dùng App facade.
1681531122666.png

Binding A Singleton
Cách binding này sử dụng thuật ngữ "Signleton" trong design patterm, tức là chỉ có 1 instance duy nhất dù ta có tạo mới instance nhiều lần. Khi ta binding 1 class vào Service Container qua cách Binding A Singleton thì các lần resolve class này ra dùng thì ta chỉ resolve 1 instance duy nhất. Hãy theo dõi ví dụ sau để hiểu hơn nhé! Ví dụ ta có 1 class TestClass muốn binding và Service Container app\Support\TestClass.php
1681531143044.png


Trong AppServiceProvider.php ta sẽ binding theo 2 cách
  1. là singleton
  2. là binding thông thường
1681531151060.png


Và đây là test kết quả
1681531159168.png


Bạn thấy sự khác biệt chưa. Cách 1 do ta binding theo kiểu singleton nên khi resolve app('test1') lần đầu tiên tạo instance , các lần sau sử dụng lại instance ở lần , nên các giá trị chạy hàm increase() sẽ tăng 1, 2, 3 Cách 2 ta binding theo kiểu thông thường nên mỗi lần resolve app('test1') sẽ tạo 1 instance khác nhau, nên các giá trị chạy hàm increase() chỉ là 1

Binding Instances
Như nói ở phần cấu trúc chung, binding này rất linh hoạt nên nếu thích ta có thể binding cả 1 instance vào.
1681531166570.png

Trong thực tế đôi khi 1 class phức tạp, ta không chắc nếu binding class này vào Service Container liệu Service Container sẽ auto injection đủ những class depency ta cần vào hay chưa, hoặc việc injection các class depency cần 1 vài thủ tục rườm rà khác. Nên Laravel cung cấp cả cách thức giúp ta binding cả 1 instance vào Service Container. Như ví dụ trên, muốn sử dụng 1 class SendGrid( 1 dịch vụ gửi email) ta cần khởi tạo class kèm theo secret key "xxx-yyy-zzz") của nó, vì vậy ta sẽ lựa chọn dùng Binding Instance

Binding Primitives
Thỉnh thoảng bạn có một class nhật một vài injected class khác, nhưng cũng cần một inject giá trị nguyên thủy như một số nguyên. Bạn có thể dễ dàng sử dụng binding để inject bất kỳ giá trị nào vào trong class nếu cần:
1681531186413.png

2.2 Binding Interfaces To Implementations
Service Container cung cấp 1 cách thức cho phép ta binding 1 interface và kèm theo 1 implementation. Hãy xem xét ví dụ sau. Ta có 1 interface là FileStorageInterface có 2 việc chính là upload và delete file. Có 1 vài class con implement interface FileStorageInterface ví dụ như: S3FileStorage , LocalFileStorage, ...
1681531197846.png


Ví dụ ta muốn dùng FileStorageInterface với triển khai là S3FileStorage, ta sẽ binding vào Service Container như sau
1681531211883.png

Rồi ở trong FileStorageController chẳng hạn, ta muốn dùng function upload(), ta sẽ dùng như sau
1681531219181.png

Nếu đến thế này thôi thì ta chưa thấy tác dụng của việc Binding Interfaces To Implementations, nhưng nếu một ngày đẹp trời ta ko muốn lưu file trên s3 mà muốn lưu file ở luôn server thì ta chỉ cần sửa đoạn code binding

1681531228144.png

Mà không cần sửa ở controller hay bất cứ chỗ nào dùng 2 function upload() hay delete() của FileStorageInterface, thật nhàn phải ko nào. Ta nói dùng Binding Interfaces To Implementations giúp code của ta: Ít phụ thuộc(less coupled), dễ bảo trì(maintainable) và dễ test hơn(testable).

2.3 Contextual Binding
Lấy ví dụ ở 2.3 trên. Nếu chúng ta thêm 1 yêu cầu như sau: Ta có 2 controller là PhotoController và VideoController, PhotoController dùng S3FileStorage, VideoController dùng LocalFileStorage thì trong Laravel sẽ có thể viết theo ngữ cảnh như thế này
1681531236067.png

Ta thấy Contextual Binding là 1 kiểu mở rộng của Binding Interfaces To Implementations khi dùng thêm điều kiện ngữ cảnh ->when. Với cách dùng này cách dùng của ta sẽ linh hoạt hơn trong từng ngữ cảnh.

2.4 Tagging
Thỉnh thoảng, bạn cần giải quyết tất cả các "category" của binding. Ví dụ, có lẽ bạn đang xây dụng một tập hợp báo cáo mà sẽ nhận một mảng danh sách các implementations khác nhau của interface Report. Sau khi đăng ký Report implementations, bạn có thể gán chúng vào một tag sử dụng phương thức tag:
1681531244237.png

Khi service đã được tag, bạn có thể dễ dàng resolve chúng qua phương thức tagged:
3. Resolving
3.1 Make
Bạn có thể sử dụng phương thức make để resolve một class instance ra khỏi container. Phương thức make nhận tên class hay interface bạn muốn thực hiện resolve
1681531253817.png

3.2 MakeWith
Ví dụ bạn dùng make mà ko thể resolve được instance của ClassA từ Service Container bởi vì ClassA này khi khởi tạo cần truyền thêm tham số vào, ví dụ là tham số id. Khi đó ta sẽ dùng makeWith
1681531259280.png
 
Bên trên