I. Zero Configuration Resolution
II. Binding

  1. Basic Binding
    1.1 Simple Binding
    1.2 Binding a Singleton
    1.3 Binding Scoped Singletons
    1.4 Binding Instances
  2. Binding Interfaces to Implementations
  3. Contextual Binding
  4. Binding Primitives
  5. Binding Typed Variadics
    5.1 Variadic Tag Dependencies
  6. Tagging
  7. Extending Bindings

III. Resolving


Chúng ta đã đi qua Basic Binding, một kiến thức cơ bản nhưng cực kỳ quan trọng. Giờ chúng a sẽ tìm hiểu tiếp các Binding còn lại nhé. Hơi dài, chịu hó đọc từ từ nhé các bợn =))

2. Binding Interfaces to Implementations

Gỉa sử bạn có một interface, gọi là EventPusher. Và bạn có một implementation (tức là một class implement interface kia), gọi là RedisEventPusher
Bây giờ nếu các bạn muốn inject interface EventPusher vào một class nào đó, dĩ nhiên các bạn phải bind interface này vào Container. Và ở đây, khi bind vào Container các bạn sẽ chỉ ra class đã implement nó

Cách dùng:

use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;
 
$this->app->bind(EventPusher::class, RedisEventPusher::class);

3. Contextual Binding

Sau khi đọc về binding interfaces ở trên, chắc bạn sẽ thắc mắc: Nếu có nhiều implementation thì sao ? Vâng, Contextual Binding giúp ta giải quyết vấn đề này

Xem ví dụ dưới đây

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;

// Filesystem là một interface
// Storage::disk('local'), Storage::disk('s3') hiểu đơn giản là 2 implementions

// Đối với controller PhotoController thì bind implementaion Storage::disk('local')
$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });
// Đối với 2 controllers VideoController, UploadController thì bind implementation Storage::disk('s3')
$this->app->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

4. Binding Primitives

Primitives (Dịch ra là nguyên thủy) 🙂 Hiểu đơn giản, các bạn muốn khởi tạo một giá trị mặc định cho một variable trong một class, các bạn có thể làm như sau

đây là class ví dụ

class UserController
{
    protected $variableName;
    
    __construct($variableName) {}
}

Giờ muốn khởi tạo giá trị mặc định thì làm như sau

use App\Http\Controllers\UserController;
 
$this->app->when(UserController::class)
          ->needs('$variableName')
          ->give('hello world');

Như vậy khi classs đc gọi tới thì giá trị mặc định của $variableName sẽ là “hello world”

Thực ra Primitives cũng thuộc về khái niệm Contextual. Binding Contextual hiểu rộng ra thì nó không chỉ giải quyết bài toán interface có nhiều implemetation, mà nó còn giúp define giá trị mặc định của các variables.

Không những thế, Contextual còn giúp xử lý các dạng dependences có dạng Array, như ở phần 5 dưới đây.

5. Binding Typed Variadics

Xem class dưới đây

<?php
 
use App\Models\Filter;
use App\Services\Logger;
 
class Firewall
{
    /**
     * The filter instances.
     *
     * @var array
     */
    protected $filters;
 
    /**
     * Create a new class instance.
     */
    public function __construct(
        protected Logger $logger,
        Filter ...$filters,
    ) {
        $this->filters = $filters;
    }
}

class Firewall có inject một mảng các instance của Filter. Để có thể inject dependence này vào thì làm như sau

$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give(function (Application $app) {
                return [
                    $app->make(NullFilter::class),
                    $app->make(ProfanityFilter::class),
                    $app->make(TooLongFilter::class),
                ];
          });

Hoặc để cho ngắn gọn thì viết như sau

$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give([
              NullFilter::class,
              ProfanityFilter::class,
              TooLongFilter::class,
          ]);

Phần 5.1 Variadic Tag Dependencies mình sẽ nói sau, khi đã đi qua phần 6. Tagging

6. Tagging

Gỉa sử các bạn có rất nhiều loại reports (CpuReport, MemoryReport, …). Các report này là implementations của report interface. Giờ chúng ta nhóm cá reports này lại sau khi đã binding

$this->app->bind(CpuReport::class, function () {
    // ...
});
 
$this->app->bind(MemoryReport::class, function () {
    // ...
});
 
$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');

Giờ muốn lấy các dependences này thì đơn giản là gọi $app->tagged(‘reports’)

$this->app->bind(ReportAnalyzer::class, function (Application $app) {
    return new ReportAnalyzer($app->tagged('reports'));
});

Quay trở lại phần 5.1, giả sử class của chúng ta có thểm Reports …$reports (tức là một mảng các report). Chúng ta làm như sau để inject chúng vào

$this->app->when(ReportAggregator::class)
    ->needs(Report::class)
    ->giveTagged('reports');

7. Extending Bindings

Tham khảo ví dụ: https://viblo.asia/p/tap-6-service-container-laravel-RQqKL2drl7z

Gỉa sử bạn có một Service đã được resolved. Ví dụ như: app/User.php

public function test()
{
    return "test() method of User class";
}

Nếu muốn override lại function test, chúng ta tạo thêm một class app/DecoratedUser.php

protected $user;

public function __construct(User $user)
{
    $this->user = $user;
}

public function decoratedTest()
{
    return 'Decorated: ' . $this->user->test();
}

Sau đó thì extend nó như sau:

// Biến $user trong closure function dùng là thể hiện cho class App\User
$this->app->extend('App\User', function($user) {
    return new \App\DecoratedUser($user);
});

Khi resolve như sau

Route::get('/', function() {
    return app()->make('App\User')->decoratedTest();
});

Các bạn có thể thấy, mặc dù đang resolve App\User nhưng chúng ta có thể gọi tới function decoratedTest của app/DecoratedUser.php, đó là do chúng ta đã binding extending ở phía trên.

Cuối cùng cũng tới phần cuối

III. Resolving

Để resolve một class từ Container thì làm như sau

use App\Services\Transistor;

// Dùng $this->app->make
$transistor = $this->app->make(Transistor::class);

Hoặc

use App\Services\Transistor;
use Illuminate\Support\Facades\App;

// Sử dụng App facade
$transistor = App::make(Transistor::class);
// Hoặc sử dụng app() helper
$transistor = app(Transistor::class);

Khi muốn resolve class cùng với tham số mặc định

use App\Services\Transistor;
 
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

Trong “controller”, “middleware”, “event listener”… Laravel cung cấp cơ chế tự động resolve
(gọi là Automatic Injection)

Xem ví dụ

<?php
 
namespace App\Http\Controllers;
 
use App\Repositories\UserRepository;
use App\Models\User;
 
class UserController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
 
    /**
     * Show the user with the given ID.
     */
    public function show(string $id): User
    {
        $user = $this->users->findOrFail($id);
 
        return $user;
    }
}

Các bạn thấy UserRepository sẽ được tự động resolve khi các bạn khai báo vào __construct function.

Và thi thoảng các bạn muốn vừa resolve class, vừa gọi tới một function của nó.
Ví dụ các bạn có class sau

<?php
 
namespace App;
 
use App\Repositories\UserRepository;
 
class UserReport
{
    /**
     * Generate a new user report.
     */
    public function generate(UserRepository $repository): array
    {
        return [
            // ...
        ];
    }
}

Bây giờ vừa resolve class trên vừa gọi tới function generate thì làm như sau

use App\UserReport;
use Illuminate\Support\Facades\App;
 
$report = App::call([new UserReport, 'generate']);

Cuối cùng, nếu bạn muốn “gửi đi một sự kiên (event)” khi class được resolve thì làm như sau

use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
 
$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
    // Khi resolve class Transistor thì xử lý đoạn code này ...
});
 
$this->app->resolving(function (mixed $object, Application $app) {
    // Khi resolve bất khi bất kì classes nào thì xử lý đoạn code này ...
});

Oke! Vậy là cũng tương đối đầy đủ về Serivce Container. Khi đọc hết 4 phần, tôi nghĩ các bạn đã hiểu “lờ mờ” nó là cái gì. Để hiểu hơn thì chúng ta phải nghiền ngẫm lại, tìm hiểu thêm, đặc biệt là phải va chạm các bài toán thực tế.
Anyway, không sao cả. Cứ keep in mind là với giai đoạn hiện tại, chúng ta chỉ cần hiểu lý thuyết ở mức “tương đối” như vậy thôi. Tôi sẽ cùng các bạn tiếp tục khám phá trong các bài tiếp theo, mọi thứ sẽ dần dần được sáng tỏ hơn.

Chúc các bạn thành công và giữ vững động lực học tập 🙂

By HNK

Leave a Reply

Your email address will not be published. Required fields are marked *