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
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
| Type | Tạo instance | Phạm vi | Sử dụng khi |
|---|---|---|---|
singleton | 1 lần | Toàn bộ app | Services không có request state (Config, Database, Cache) |
transient | Mỗi lần resolve | Không giới hạn | Services cần instance mới (Mailer, HTTP Client) |
scoped | 1 lần/coroutine | Per-request | Services chứa request state (Request, Session) |