Nhảy tới nội dung

Service Container

Giới thiệu

Service Container (hay IoC Container) là trái tim của PHP Framework. Nó quản lý việc đăng ký, resolve và inject dependencies cho toàn bộ ứng dụng. Container hỗ trợ PSR-11 và được tối ưu cho môi trường Swoole với coroutine-aware scoping.

Binding

Singleton

Đăng ký service được chia sẻ - chỉ tạo một instance duy nhất trong suốt vòng đời ứng dụng:

$this->app->singleton('cache', fn ($app) => new CacheManager($app));
// Mọi lần resolve đều trả về cùng một instance
$cache1 = $app->get('cache');
$cache2 = $app->get('cache');
// $cache1 === $cache2 → true

Sử dụng singletonIf để chỉ đăng ký nếu chưa tồn tại:

$this->app->singletonIf('cache', fn ($app) => new CacheManager($app));

Transient

Đăng ký service tạo instance mới mỗi lần resolve:

$this->app->transient('mailer', fn ($app) => $app->get('mailer_factory')->driver());
// Mỗi lần resolve tạo instance mới
$mailer1 = $app->get('mailer');
$mailer2 = $app->get('mailer');
// $mailer1 === $mailer2 → false

Scoped

Đăng ký service có phạm vi theo coroutine (tương đương per-request trong Swoole). Instance được tạo một lần cho mỗi request và tự động cleanup khi request kết thúc:

$this->app->scoped('request', fn () => new Request());
// Trong cùng một request: cùng instance
$req1 = $app->get('request');
$req2 = $app->get('request');
// $req1 === $req2 → true

// Request khác: instance mới
Khi nào dùng Scoped?

Sử dụng scoped cho các service chứa state liên quan đến request hiện tại, ví dụ: Request. Điều này đảm bảo mỗi request trong Swoole có state riêng biệt.

Resolve Services

Sử dụng get()

// Resolve bằng service ID
$cache = $app->get('cache');

// Resolve bằng class name (nếu có alias)
$cache = $app->get(CacheManager::class);

Sử dụng helper app()

// Lấy App instance
$app = app();

// Resolve service
$cache = app('cache');
$db = app('database');

Sử dụng App::service()

use Vietiso\Core\App;

$cache = App::service('cache');
$db = App::service('database');

Sử dụng App::make()

Tạo instance của một class với automatic dependency injection:

// Tự động inject constructor dependencies
$controller = App::make(UserController::class);

// Với parameters bổ sung
$service = App::make(PaymentService::class, [
'apiKey' => 'sk_test_xxx',
]);

Sử dụng App::call()

Gọi một method/closure với automatic dependency injection:

// Gọi closure
$result = App::call(function (CacheManager $cache, Request $request) {
return $cache->get('key');
});

// Gọi method trên object
$result = App::call([$controller, 'index'], ['id' => 1]);

Aliases

Đăng ký alias cho phép resolve service bằng nhiều tên khác nhau:

// Đăng ký
$this->app->singleton('database', fn ($app) => new ConnectionManager($app->get('pool')));
$this->app->alias('database', ConnectionManagerInterface::class);

// Resolve - cả 2 đều trả về cùng instance
$db1 = $app->get('database');
$db2 = $app->get(ConnectionManagerInterface::class);
// $db1 === $db2 → true

Pattern phổ biến: đăng ký service bằng string ID ngắn gọn, alias bằng interface class:

$this->app->singleton('cache', fn () => new CacheManager($this->app));
$this->app->alias('cache', CacheManagerInterface::class);

$this->app->singleton('events', fn () => new EventListenerProvider());
$this->app->alias('events', EventListenerProviderInterface::class);

Property Injection

Ngoài constructor injection, Container hỗ trợ property injection bằng #[Inject] attribute:

use Vietiso\Core\Container\Attributes\Inject;

class UserController
{
#[Inject]
protected CacheManager $cache;

#[Inject]
protected LoggerFactory $logger;

public function index()
{
// $this->cache và $this->logger đã được inject
}
}

Kiểm tra Service

// Kiểm tra service đã đăng ký chưa
if ($app->has('cache')) {
$cache = $app->get('cache');
}

ArrayAccess

Container implement ArrayAccess, cho phép sử dụng cú pháp array:

// Tương đương $app->get('config')
$config = $app['config'];

// Kiểm tra tồn tại
if (isset($app['cache'])) {
// ...
}

Scoped Instances & Swoole

Trong môi trường Swoole, mỗi request chạy trong một coroutine riêng. Container sử dụng coroutine ID để quản lý scoped instances:

// Mỗi coroutine có riêng scoped instances
$coroutineId = \Swoole\Coroutine::getuid();

// Cleanup sau mỗi request
$app->forgetScopedInstances();

Copy Scoped Instances

Khi cần chia sẻ scoped instances giữa các coroutines (ví dụ: parent → child coroutine):

$app->copyScopedInstances($fromCoroutineId, $toCoroutineId);

Container trong Service Provider

Trong Service Provider, container được truy cập qua property $this->app:

class CacheServiceProvider extends ServiceProvider
{
public function register(): void
{
// $this->app là Container instance
$this->app->singleton('cache', fn () => new CacheManager($this->app));
}

public function boot(): void
{
$config = $this->app->get('config');
// Khởi tạo cache drivers từ config
}
}

Tổng kết Binding Types

TypeTạo instancePhạm viSử dụng khi
singleton1 lầnToàn bộ appServices không có request state (Config, Database, Cache)
transientMỗi lần resolveKhông giới hạnServices cần instance mới (Mailer, HTTP Client)
scoped1 lần/coroutinePer-requestServices chứa request state (Request, Session)