X

Laravel: Tìm hiểu về Service Container

Giới thiệu về Service Container trong laravel

Laravel service container là một công cụ rất mạnh trong việc quản lý các class dependencies và thực hiện xử lý dependency injection. Dependency injection là một cụm từ thể hiện có nghĩa là: các dependencies của class được "injected" vào trong class thông qua hàm khởi tạo hoặc trong một số trường hợp là các phương thức "setter". Hãy xem ví dụ đơn giản dưới đây:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * The user repository implementation.
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

Trong ví dụ trên, UserController cần lấy thông tin người dùng từ một nguồn dữ liệu. Vì vậy, chúng ta sẽ inject vào một service có thể truy suất người dùng. Trong hoàn cảnh này, UserRepository có thể sử dụng Eloquent để lấy lại thông tin người dùng từ cơ sở dữ liệu. Tuy nhiên, khi repository được inject, chúng ta có thể dễ dàng trao đổi chúng với implementation khác. Chúng ta cũng dễ dàng "mock", hoặc tạo một dummy implementation của UserRepository khi testing ứng dụng của bạn.

Sự hiểu biết sâu về Laravel service container là rất cần thiết cho việc phát triển ứng dụng mạnh mẽ,lớn và đóng góp cho core của Laravel.

Binding

Binding Basics

Hầu như tất cả các service container binding của bạn sẽ được đăng Ký trong , vì vậy, hầu hết những ví dụ này sẽ minh hoạ cách sử dụng container trong bối cảnh đó.

 

Simple Bindings

 

Bên trong một service provider, bạn luôn luôn có quyền truy cập vào trong container thông qua thuộc tính $this->app. Chúng ta có thể đăng kí liên kết sử dụng phương thức bind, và truyền vào tên của class hay interface mà chúng ta muốn đăng kí cùng với Closure thực hiện trả về instance của class đó:

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

Lưu ý rằng chúng ta nhận được container như một đối số truyền vào cho resolver. Sau đó thì chúng ta có thể thực hiện resolve các sub-dependencies con của đối tượng mà đang được xây dựng.

 

Binding A Singleton

 

Phương thức singleton thực hiện liên kết một class hoặc interface vào container mà chỉ cần thực hiện duy nhất một lần. Khi một singleton binding được giải quyết, cùng một object instance sẽ được trả về trong các subsequent calls vào container:

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

 

Binding Instances

 

Bạn cũng có thể bind một instance đang tồn tại vào trong container bằng cách sử dụng phương thức instance. Instance đó sẽ luôn luôn được trả về trong các lần gọi sau vào container:

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\Api', $api);

 

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:

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

Binding Interfaces To Implementations

 

Một tính năng tuyệt vởi của service container là nó có khả năng bind một interface thành một implementation. Ví dụ, giả sử chúng ta có interface EventPusher và một implementation RedisEventPusher. Khi đã có code của implementation RedisEventPusher cho interface, chúng ta có thể đăng ký nó với service container như sau:

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

Câu lệnh này sẽ nói với container nó sẽ inject RedisEventPusher khi một class nào đó cần một implementations từ interface EventPusher. Chúng ta có thể type-hint interface EventPusher interface trong một constructor, hay bất cứ vị trí nào mà dependencies có thể được inject bởi service container:

use App\Contracts\EventPusher;

/**
 * Create a new class instance.
 *
 * @param    EventPusher  $pusher
 * @return  void
 */
public function __construct(EventPusher $pusher)
{
    $this->pusher = $pusher;
}

Contextual Binding

 

Đôi khi bạn sẽ có hai class triển khai từ cùng một interface nhưng bạn muốn inject các implementations khác nhau vào các class. Ví dụ, hai controllers có thể phụ thuộc vào implementations khác nhau của Illuminate\Contracts\Filesystem\Filesystem . Laravel cung cấp một interface đơn giản và liền mạch cho việc khai báo hành vi này:

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

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

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:

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

Khi service đã được tag, bạn có thể dễ dàng resolve chúng qua phương thức tagged:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

Resolving

The make Method

 

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:

$api = $this->app->make('HelpSpot\API');

Nếu bạn đang ở ví trị mà code của bạn không truy cập được biến $app, bạn có thể sử dụng helper global resolve:

$api = resolve('HelpSpot\API');

Nếu một số dependencies của class của bạn không thể resolve được thông qua container, bạn có thể inject chúng bằng cách chuyển chúng thành mảng liên kết qua phương thức makeWith:

$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);

 

Automatic Injection

 

Ngoài ra, và cũng quang trọng, bạn có thể đơn giản "type-hint" dependency vào trong hàm constructor của class nó sẽ được resolved bởi container, gồm , , , , và còn nữa. Trong thực tế, đây là cách giải quyết đối tượng của bạn sẽ được giải quyết bởi container.

Ví dụ, bạn có thể type-hint một repository được định nghĩa bởi ứng dụng trong hàm khởi tạo constructor của controller. Repository này sẽ tự động được resolv và inject vào class:

<?php

namespace App\Http\Controllers;

use App\Users\Repository as UserRepository;

class UserController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param    UserRepository  $users
     * @return  void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the user with the given ID.
     *
     * @param    int  $id
     * @return  Response
     */
    public function show($id)
    {
        //
    }
}

 

Container Events

 

Service container sẽ bắn ra các event mỗi khi nó thực hiện resolves một đối tượng. Bạn có thể listen các event qua phương thức resolving:

$this->app->resolving(function ($object, $app) {
    // Called when container resolves object of any type...
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

Như bạn có thể thấy, đối tượng đang được resolve sẽ truyền lại vào trong hàm callback, cho phép bạn thiết lập các thuộc tính bổ sung nào vào trong object trước khi được trả lại cho bên sử dụng nó.

Tài liệu: