12/08/2018, 13:07

Laravel Beauty: Tìm hiểu về Service Provider

Laravel Beauty: Recipes & Best Practices Laravel Beauty: Tìm hiểu về Service Container Laravel Beauty: Tìm hiểu về Service Provider Laravel Beauty: Tìm hiểu về Facade Laravel Beauty: Tìm hiểu về Contract Trong bài viết lần trước, chúng ta đã cùng tìm hiểu về thành phần trung ...

laravel-1.png

  1. Laravel Beauty: Recipes & Best Practices
  2. Laravel Beauty: Tìm hiểu về Service Container
  3. Laravel Beauty: Tìm hiểu về Service Provider
  4. Laravel Beauty: Tìm hiểu về Facade
  5. Laravel Beauty: Tìm hiểu về Contract

Trong bài viết lần trước, chúng ta đã cùng tìm hiểu về thành phần trung tâm của Laravel Framework, đó là Service Container.

Nhìn chung thì Service Container trong Laravel là nơi quản lý class dependency và thực hiện dependency injection.

Bạn chỉ cần bind một Class, Interface hay một từ khoá bất kỳ với Service Container là có thể resolve chúng ra ở bất cứ nơi đâu.

Bản thân chính Laravel cũng thực hiện bind rất nhiều thứ vào trong Service Container để chúng ta có thể gọi ra sử dụng khi cần thiết, bằng nhiều cách khác nhau.

Và trong bài viết này, ta sẽ tìm hiểu xem Laravel thực hiện việc bind đó khi nào, ở đâu và bằng cách nào nhé. Từ đó ta sẽ có thể tự hiểu và tìm ra cách để thay thế một service có sẵn của Laravel bằng một service do chính mình implement một cách dễ dàng.

Service Provider - Khai báo trong config/app.php

Chắc hẳn bạn đã động chạm nhiều đến file config này rồi khi làm việc với Laravel. Không chỉ là nơi setup những thông số của project như debug mod, url, timezone, local ..., ở file config/app.php, bạn sẽ còn thấy một mục quan trọng khác, đó là providers, với những dòng comment ở phía dưới là

/*
 * Laravel Framework Service Providers...
 */

/*
 * Application Service Providers...
 */

Vâng, đây chính là nơi bắt đầu của việc registering service container bindings. Tuy nhiên thay vì luôn phải sử dụng app()->bind(), app()->instance() ... như đã đề cập ở bài viết trước, thì ở phần khai báo bindings trong file config/app.php này ta lại truyền vào tên các class. Mà các class đó thường được đặt tên dưới dạng là SomethingServiceProvider, chẳn hạn như IlluminateSessionSessionServiceProvider, IlluminateViewViewServiceProvider, IlluminateRoutingControllerServiceProvider ...

Ngoài ra, chắc bạn cũng đã từng để ý là thường thì ở các package Laravel mà các bạn vẫn hay dùng, như Laravel Debugbar, Laravel Markdown, Laravel IDE Helper ..., khi cài đặt, các package đó đều yêu cầu các bạn add class ServiceProvider của chúng vào phần providers trong config/app.php. Hay nói các khác, các third-party package cho Laravel cũng thường xuyên cung cấp Service Provider để quá trình cài đặt và sử dụng được dễ dàng.

Vậy rốt cuộc Service Provider là gì?

Service Provider - Từ khái niệm đến thực tiễn

Như đã nói ở trên, trong project được tạo ra, ngoài file bootstrap/app.php có khai báo binding KernelException ra thì bạn sẽ không thấy nơi nào khai báo binding một cách trực tiếp nữa. Thay vào đó chỉ có nơi khai báo class sẽ thực hiện việc binding, những class đó chính là Service Provider.

Các Service Provider đều được extend từ một abstract class mà Laravel cung cấp, đó là IlluminateSupportServiceProvider. Nếu bạn vào tìm hiểu code của class này thì sẽ thấy nó bao gồm một abstract function là register, điều đó có nghĩa là Service Provider của bạn viết sẽ bắt buộc phải khai báo method register này. Và đây chính là nơi bạn thực hiện việc binding vào Service Container. Chẳng hạn hãy cùng xem một Service Provider đơn giản là HashServiceProvider xem nó có gì nhé.

Bình thường, vào file config/app.php bạn sẽ thấy providers đó được khai báo dưới dạng IlluminateHashingHashServiceProvider::class, dựa trên cái tên đó ta có thể tìm ra file provider tại /vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php:

class HashServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('hash', function () { return new BcryptHasher; });
    }
}

Như vậy ta có thể thấy trong HashServiceProvider ta đã thực hiện việc binding từ khoá hash, có thể coi đây là tên service, với một instance của class BcryptHasher qua method singleton. Như vậy thì:

  • get_class(app()->make('hash')) sẽ trả ra kết quả là IlluminateHashingBcryptHasher;
  • app()->make('hash') === app()->make('hash') sẽ trả ra kết quả true, do các lần gọi app()->make('hash') ta đều sẽ nhận về cùng một object.

Bên cạnh đó, các bạn có thể thấy trong class HashServiceProvider ở trên, ta còn có một biến protected là $defer. Việc khai báo $defer = true sẽ giúp cho quá trình binding sẽ không được thực hiện cho đến khi service được gọi. Hay nói cách khác, theo ví dụ ở trên thì với mỗi request, một instance của BcryptHasher sẽ không phải được tạo ra ngay từ đầu, mà chỉ khi nào ta gọi app()->make('hash') lần đầu thì việc binding mới diễn ra (và cũng sẽ chỉ diễn ra một lần, từ những lần gọi sau thì đã có trong Service Container rồi nên chỉ việc lấy ra mà thôi). Để thực hiện được việc này thì bên cạnh hàm register, trong trường hợp $defer = true thì ta cần phải có thêm một hàm nữa báo trước cho Laravel là cái provider này sẽ trả về service gì, đó là hàm provides. Cụ thể thì hàm provides của HashServiceProvider trông như sau:

public function provides()
{
    return ['hash'];
}

Như vậy thì ta có thể tóm tắt lại quá trình Laravel thực hiện xử lý các Service Provider như sau:

  • Check xem biến $defer là true hay false. Nếu là false thì thực hiện ngay việc binding thông qua việc chạy hàm register.
  • Nếu $defer là true thì không chạy hàm register, thay vào đó sẽ lưu lại thông tin về việc service provider đó sẽ trả về service gì thông qua kết quả của hàm provides. Như vậy việc binding đã không diễn ra.
  • Khi ta yêu cầu resolve một service nào đó từ Service Container, Laravel sẽ check xem đó có phải là deferred service hay không, nếu đúng thì Laravel sẽ thực hiện việc chạy hàm register của provider tương ứng. Lúc này việc binding mới được diễn ra. Và đương nhiên sau khi binding xong thì service đó sẽ được remove ra khỏi danh sách các deferred service. Cuối cùng, việc resolve sẽ được thực hiện.

Việc load các service một cách defer như vậy sẽ giúp cái thiện performance cho application của bạn, khi mà những service chỉ được load khi mà nó là cần thiết. Đương nhiên nếu service của bạn là chắc chắn cần thiết, chắc chắn phải sử dụng thì bạn không cần khai báo $defer = true làm gì, nó sẽ chỉ làm tăng thêm xử lý khi gọi ra lần đầu mà thôi.

Ngoài ra, còn một chú ý khác nữa là bạn chỉ nên thực hiện khai báo binding trong hàm register, chứ không nên khai báo những event listener, routes hay các xử lý phức tạp khác. Bởi nên biết rằng Laravel sẽ duyệt qua một loạt các provider để thực hiện việc binding, thế nên có khả năng bạn sẽ gặp phải tình huống là gọi ra một service khi mà provider của nó còn chưa được xử lý.

Còn nếu muốn viết những xử lý có yêu cầu những service khác, thì bạn nên viết nó trong hàm boot, bởi hàm này sẽ chỉ được gọi khi mà toàn bộ các service provider đã được duyệt qua, và vì thế, đương nhiên là bạn sẽ có thể access được vào toàn bộ các service.

Service Provider - Ứng dụng

Service Provider chính là chìa khoá cho quá trình bootstrapping một Laravel Application. Hãy tưởng tượng application của bạn như một cái Container, và khi khởi chạy, nó sẽ tiến hành đưa các service cần thiết vào trong container đó, rồi những gì bạn cần làm là lấy ra những service cần thiết vào thời điểm cần thiết từ container để xử lý một request gửi đến.

Việc được xây dựng dựa trên sự kết hợp của các service như vậy đã giúp Laravel có sự mềm dẻo và linh hoạt cần thiết. Trong quá trình làm việc với Laravel, khi đã nắm rõ khái niệm, vai trò và cách thức sử dụng Service Provider thì bạn hoàn toàn có thể ứng dụng nó vào các trường hợp chẳng hạn như:

  • Bootstrapping application: Khi nhìn vào phần khai báo providers trong config/app.php, bạn sẽ thấy sẽ có phần cho Application Service Providers, và trong đó thì có AppProvidersAppServiceProvider. Laravel đã tạo sẵn cho bạn một nơi lý tưởng để bạn có thể thêm vào những binding hay những bootstrapping config của mình. Chặng hạn như bạn muốn viết thêm những validation của riêng mình thông qua việc sử dụng Validator::extend, hay muốn thêm vào những HTML Macro, hay bất cứ xử lý logic nào khác bạn cần chạy trước khi tiến hành xử lý request ..., thì hàm boot trong AppServiceProvider là nơi bạn nên thực hiện những việc đó việc đó.
  • Tự viết các service của mình: Nếu application của bạn có quá nhiều thứ cần bootstrap mà bạn cảm thấy viết hết vào hàm boot trong AppServiceProvider là không tốt thì bạn nên nghĩ đến viết nhiều Service Provider khác nhau. Ngoài ra khi mà application của bạn được mở rộng, và có cấu trúc lớn thì một việc quan trọng được đặt ra là cần phải tổ chức các business logic như thế nào cho hợp lý. Và đương nhiên việc viết thành những service rồi khai báo qua Service Provider là một trong những cách mà bạn nên thử. Chẳng hạn như application của bạn có nhiều chỗ đòi hỏi làm việc với image, và đi cùng với đó sẽ là những hàm, những xử lý với image. Có thể khi đó bạn sẽ cần đến một Image class là nơi chứa các logic function, và một ImageServiceProvider, với những xử lý binding và bootstrapping ở trong đó             </div>
            
            <div class=
0