Quay lại
Service Container Trong Laravel

Service Container trong Laravel là gì?

Service Container trong Laravel là một công cụ mạnh mẽ để quản lý phụ thuộc (dependencies) và thực hiện Dependency Injection. Nó là một phần cốt lõi của framework, giúp tạo ra và quản lý các đối tượng, xử lý các phụ thuộc và giúp ứng dụng trở nên dễ bảo trì và kiểm thử hơn.

Để dễ hiểu hơn thì chúng ta xem lại ví dụ ở bài trước, chúng ta đã hiểu về Dependency Injection và các ví dụ về nó. VD:

// Định nghĩa interface
interface UserRepositoryInterface {
    public function getAll();
}

// Triển khai interface
class UserRepository implements UserRepositoryInterface {
    public function getAll() {
      // Thực hiện lấy tất cả người dùng từ cơ sở dữ liệu
      return ['user1', 'user2', 'user3'];
    }
}

// Class sử dụng DI thông qua interface
class UserService {
    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getAllUsers() {
        return $this->userRepository->getAll();
    }
}

// Khởi tạo đối tượng UserRepository
$userRepository = new UserRepository();

// Khởi tạo đối tượng UserService và truyền UserRepository vào
$userService = new UserService($userRepository);

// Sử dụng UserService để lấy tất cả người dùng
$users = $userService->getAllUsers();
// Output: user1, user2, user3​

Trong phương pháp trên này, chúng ta phải tự tay khởi tạo tất cả các đối tượng và quản lý chúng. Điều này có thể trở nên phức tạp khi số lượng dependency tăng lên.

Bây giờ chúng ta hãy cùng đi xem cách mà Service Container quản lý phụ thuộc (dependencies) và thực hiện Dependency Injection sau đó thì chúng ta sẽ đi vào chi tiết cách mà nó thực hiện.

1. Định nghĩa Interface và Implementation

// Định nghĩa interface
interface UserRepositoryInterface {
    public function getAll();
}

// Triển khai interface
class UserRepository implements UserRepositoryInterface {
    public function getAll() {
        // Thực hiện lấy tất cả người dùng từ cơ sở dữ liệu
        return ['user1', 'user2', 'user3'];
    }
}​

2. Đăng ký binding vào Container

// Trong một service provider hoặc bootstrap file vd: App\Providers\AppServiceProvider.php
$app->bind(UserRepositoryInterface::class, UserRepository::class);​

3. Sử dụng Service Container trong Class khác

class UserService {
    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getAllUsers() {
        return $this->userRepository->getAll();
    }
}

class UserController {
    public $userService;

    public function __construct(UserService $userService) {
        $this->userService = $userService;
    }

    public function getUsers() {
        echo $this->userService->getAllUsers();
    }
}​

4. Resolve Dependencies và sử dụng

Nếu chúng ta muốn sử dụng các phương thức trong mội đối tượng mà chúng ta đã binding thi đơn giản chỉ cần tạo đối tượng thông qua Service Container như bên dưới, bên dưới chỉ là một ví dụ cách mà nó lấy một đối tượng từ container còn các bạn có thể return $this->userService->getAllUsers();

// Tạo đối tượng UserService thông qua Service Container
$users = app(UserController::class);
$users->getUsers();
// Output: user1, user2, user3​

Trong ví dụ này, UserRepositoryInterface được bind với UserRepository trong container. Khi UserServiceUserController yêu cầu UserRepositoryInterface, container sẽ tự động inject UserRepository. Điều này giúp mã nguồn gọn gàng hơn, dễ bảo trì và test hơn.

Tại sao lại dùng Service Container?

  • Quản lý Dependency: Service Container giúp quản lý các dependency của class một cách tự động. Bạn không cần phải tự tay tạo và truyền các đối tượng cần thiết, Service Container sẽ làm điều đó cho bạn.
  • Dễ bảo trì và mở rộng: Khi các class không phụ thuộc chặt chẽ vào nhau, bạn có thể dễ dàng thay đổi hoặc mở rộng chức năng mà không ảnh hưởng đến các phần khác của ứng dụng.
  • Tăng khả năng test: Dependency Injection qua Service Container làm cho việc viết các unit test trở nên dễ dàng hơn vì bạn có thể dễ dàng mock các dependency.

Khi nào sử dụng Service Container?

  • Khi bạn cần quản lý các dependency phức tạp.
  • Khi bạn muốn tăng tính module hóa và khả năng test của ứng dụng.
  • Khi bạn muốn sử dụng các service mà Laravel cung cấp thông qua dependency injection.

Các cách binding trong Service Container

Trước khi chúng ta vào các loại binding mình đưa ra một ví dụ sau:

Bước 1: Tạo Interface và Implementation

// Định nghĩa interface
interface ConfigManagerInterface {
    public function get($key);
    public function set($key, $value);
}

// Triển khai interface
class ConfigManager implements ConfigManagerInterface {
    protected $config = [];

    public function get($key) {
        return $this->config[$key] ?? null;
    }

    public function set($key, $value) {
        $this->config[$key] = $value;
    }
}

Bước 2: Bind class vào container -> chúng ta sẽ áp dụng từng loại Bind ở ví dun này ở bên dưới

Bước 3: Sử dụng ConfigManager trong hai file khác nhau

FirstService.php:

namespace App\Services;

use ConfigManagerInterface;

class FirstService {
    protected $configManager;

    public function __construct(ConfigManagerInterface $configManager) {
        $this->configManager = $configManager;
    }

    public function setConfig($key, $value) {
        $this->configManager->set($key, $value);
    }

    public function getConfig($key) {
        return $this->configManager->get($key);
    }
}

SecondService.php:

namespace App\Services;

use ConfigManagerInterface;

class SecondService {
    protected $configManager;

    public function __construct(ConfigManagerInterface $configManager) {
        $this->configManager = $configManager;
    }

    public function getConfig($key) {
        return $this->configManager->get($key);
    }
}

Bước 4: Sử dụng các service trong ứng dụng

Trong controller hoặc bất kỳ nơi nào bạn muốn sử dụng các service này:

namespace App\Http\Controllers;

use App\Services\FirstService;
use App\Services\SecondService;

class ConfigController extends Controller
{
    protected $firstService;
    protected $secondService;

    public function __construct(FirstService $firstService, SecondService $secondService)
    {
        $this->firstService = $firstService;
        $this->secondService = $secondService;
    }

    // Các hàm xử lý logic
    ...
}

1. Binding simple

Một cách để đăng ký một Class hoặc một interface với Service Container mà không cần cung cấp logic phức tạp để tạo ra các instance của Class đó. Thay vào đó, Laravel sẽ tự động tạo các instance mỗi khi dịch vụ được yêu cầu.

Trong file AppServiceProvider.php hoặc một service provider tùy chỉnh:

// Binding một class cụ thể vào container
public function register()
{
    $this->app->bind(ConfigManagerInterface::class, ConfigManager::class);
}

Giả sử chúng ta có 2 biến $key$value được truyền qua URL:

// Định nghĩa logic trong ConfigController với hàm index như sau:
public function index($key, $value)
{
    // Set a config value using FirstService
    $this->firstService->setConfig($key, $value);

    // Get config value using SecondService
    return $this->firstService->getConfig($key);
}

Test:

$config1 = app(ConfigController::class);
echo $config->index('config_1', 'config Example 1');
//Output: config Example 1

$config2 = app(ConfigController::class);
echo $config->index('config_1', 'config Example 2');
/Output: config Example 2

2. Binding với Closure

- Sử dụng Closure để cung cấp một hàm để tạo ra instance của service.

- Closure binding cho phép bạn cung cấp một hàm Closure, mà Laravel sẽ gọi khi cần tạo một instance của dịch vụ. Điều này cho phép bạn kiểm soát cách mà dịch vụ được khởi tạo.

- Thích hợp khi bạn cần kiểm soát logic phức tạp hơn khi khởi tạo một dịch vụ, hoặc cần sử dụng các tham số từ container.

$this->app->bind(ConfigManagerInterface::class, function ($app) {
    return new ConfigManager();
});

Ví dụ trong class ConfigManager có một con __construct có tham số truyền vào thì chúng ta sử dụng theo cách Binding Closure này vd:

/ Triển khai interface
class ConfigManager implements ConfigManagerInterface {

    protected $config = [];
    protected $otherConfig;

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

    ...
}
$this->app->bind(ConfigManagerInterface::class, function ($app) {
    return new ConfigManager($otherConfig);
});

Test: Vẫn sử dụng như các binding simple

3. Singleton Binding

Singleton Binding là một loại binding trong Laravel Service Container, đảm bảo rằng chỉ có một instance duy nhất của một class được tạo ra và sử dụng trong toàn bộ ứng dụng. Điều này có nghĩa là khi bạn yêu cầu class đó từ container nhiều lần, bạn sẽ luôn nhận được cùng một instance.

Lợi ích:

  • Hiệu quả: Tránh tạo nhiều instance không cần thiết, giảm thiểu việc sử dụng tài nguyên hệ thống.
  • Trạng thái chia sẻ: Dễ dàng quản lý và chia sẻ trạng thái hoặc cấu hình giữa các phần khác nhau của ứng dụng.
  • Dễ bảo trì: Đảm bảo rằng các thay đổi được áp dụng nhất quán trong toàn bộ ứng dụng.

Cách sử dụng:

// Singleton binding, chỉ tạo một instance duy nhất
public function register(): void
{
    $this->app->singleton(ConfigManagerInterface::class, ConfigManager::class);
}

Test:

// Định nghĩa logic trong ConfigController với hàm config như sau:
public function config()
{
    // Set a config value using FirstService
    $this->firstService->setConfig('site_name', 'Laravel Example');

    // Get the same config value using SecondService
    $siteName = $this->secondService->getConfig('site_name');

    return $siteName; // Output: 'Laravel Example'
}

$config = app(ConfigController::class);
$config->config();
Giải thích:
  1. Binding Singleton: Chúng ta đã bind ConfigManagerInterface vào ConfigManager dưới dạng singleton trong service provider.
  2. Sử dụng trong các service: Trong FirstServiceSecondService, chúng ta đã yêu cầu ConfigManagerInterface thông qua constructor injection.
  3. Lấy cùng một instance: Khi chúng ta lấy ConfigManagerInterface từ container trong cả FirstServiceSecondService, chúng ta nhận được cùng một instance của ConfigManager.
  4. Trạng thái chia sẻ: Bất kỳ thay đổi nào được thực hiện bởi FirstService đối với ConfigManager đều có thể được truy cập bởi SecondService và ngược lại, vì cả hai đều sử dụng cùng một instance của ConfigManager.

Sử dụng khi: Khi bạn muốn container tự quản lý và đảm bảo chỉ có một instance duy nhất của class trong suốt vòng đời của ứng dụng.

4. Instance Binding

Instance Binding cho phép bạn đăng ký một instance cụ thể của một class vào container. Điều này có nghĩa là bạn tự tạo một instance của class đó và sau đó đăng ký nó vào container. Mỗi lần bạn yêu cầu class này từ container, nó sẽ trả về cùng một instance đã được đăng ký.

Ví dụ, nếu bạn đã tạo một instance của một class và muốn sử dụng nó trong toàn bộ ứng dụng, bạn có thể sử dụng Instance Binding để đăng ký instance này vào Service Container và sau đó sử dụng nó bằng cách gọi tên của instance đã đăng ký.

$instance = new ClassA;
$this->app->instance('key', $instance);

Ví dụ:

public function register()
{
    // Tạo một instance cụ thể của ConfigManager
    $configManager = new ConfigManager();

    // Đăng ký instance này vào container
    $this->app->instance(ConfigManagerInterface::class, $configManager);
}

Khi đã đăng ký instance cụ thể của ConfigManager vào container, chúng ta có thể sử dụng nó trong các dịch vụ FirstServiceSecondService như sau:

public function index()
{
    // Đặt cấu hình sử dụng FirstService
    $this->firstService->setConfig('app_name', 'My Application');

    // Lấy cấu hình sử dụng SecondService
    $configValue = $this->secondService->getConfig('app_name');

    return response()->json(['config_value' => $configValue]);
}

Test:

$manager = app(ConfigController::class);
echo $manager->config();

Trong ví dụ này, cả FirstServiceSecondService đều sử dụng cùng một instance của ConfigManager. Khi FirstService thiết lập một giá trị cấu hình, giá trị này có thể được lấy lại bởi SecondService. Điều này cho thấy cách sử dụng Instance Binding để đảm bảo rằng các dịch vụ chia sẻ cùng một instance của một class cụ thể.

Sử dụng khi: Khi bạn cần tạo và cấu hình instance trước khi đăng ký vào container.

Resolve Dependencies Từ Service Container

Service Container trong Laravel cung cấp các phương thức truy xuất đến các đối tượng đã được đăng ký trước đó. Các phương thức này bao gồm resolve()app() Facade và Dependency Injection.

Sử dụng resolve():

Phương thức resolve() cho phép chúng ta truy xuất đến các đối tượng đã được đăng ký với Service Container. Ví dụ, để truy xuất một đối tượng đã được đăng ký, chúng ta có thể sử dụng phương thức resolve():

$object = resolve(ClassName::class);

Trong đó, ClassName là tên lớp cụ thể mà chúng ta muốn truy xuất.

Sử dụng app() Facade

Facade là một tính năng quan trọng của Laravel, giúp cho việc truy xuất các đối tượng trở nên dễ dàng hơn. Service Container trong Laravel cũng cung cấp Facade để truy xuất đến các đối tượng đã được đăng ký với Service Container. Ví dụ, để truy xuất một đối tượng đã được đăng ký, chúng ta có thể sử dụng Facade:

$object = app()->make(ClassName::class);

Trong đó, ClassName là tên lớp cụ thể mà chúng ta muốn truy xuất.

Sử dụng Dependency Injection type-Hinting:

Dependency Injection là một kỹ thuật quan trọng trong lập trình phần mềm, cho phép chúng ta truyền các đối tượng vào một lớp thông qua constructor hoặc method. Service Container trong Laravel cũng hỗ trợ Dependency Injection. Ví dụ, để truy xuất một đối tượng đã được đăng ký với Service Container thông qua Dependency Injection, chúng ta có thể sử dụng constructor hoặc method:

use App\Services\ServiceInterface;
 
class ServiceClass
{
    protected $service;
 
    public function __construct(ServiceInterface $service)
    {
        $this->service = $service;
    }
 
    public function someMethod()
    {
        $object = $this->service->someMethod();
    }
}

Trong đó, ServiceInterface là Interface mà đối tượng đã được đăng ký và ServiceClass là lớp chúng ta muốn truy xuất đối tượng đó thông qua Dependency Injection.

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday