Service Providers
Giới thiệu
Service Providers là nơi tập trung đăng ký và khởi tạo các services cho ứng dụng. Mọi core service của framework (Database, Cache, Mail, Routing...) đều được bootstrap thông qua Service Providers.
Khi ứng dụng khởi động, framework load danh sách providers từ core/providers.php và thực hiện 2 pha: register và boot.
Viết Service Provider
Cấu trúc cơ bản
Tất cả providers kế thừa Vietiso\Core\Container\ServiceProvider:
<?php
namespace App\Providers;
use Vietiso\Core\Container\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
// Đăng ký services vào Container
}
public function boot(): void
{
// Khởi tạo sau khi tất cả services đã đăng ký
}
}
Register Method
Method register() chỉ nên đăng ký bindings vào Container. Không nên truy cập các services khác trong method này vì chúng có thể chưa được đăng ký:
public function register(): void
{
// Đăng ký singleton
$this->app->singleton('payment', fn ($app) => new PaymentManager(
$app->get('config')
));
// Đăng ký alias
$this->app->alias('payment', PaymentManagerInterface::class);
}
Boot Method
Method boot() được gọi sau khi tất cả providers đã register. Tại đây bạn có thể an toàn truy cập bất kỳ service nào:
public function boot(): void
{
$config = $this->app->get('config');
$payment = $this->app->get('payment');
// Load cấu hình payment gateways
foreach ($config->get('payment.gateways') as $name => $gateway) {
$payment->addGateway($name, $gateway);
}
// Đăng ký event listeners
$events = $this->app->get('event_dispatcher');
$events->listen(OrderPaid::class, ProcessPayment::class);
}
Ví dụ thực tế
Provider đơn giản - Chỉ Register
Nhiều providers chỉ cần method register():
<?php
namespace Vietiso\Core\Cache;
use Vietiso\Core\Container\ServiceProvider;
class CacheServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton('cache', fn () => new CacheManager($this->app));
}
}
Provider với Boot - Database
Provider phức tạp hơn cần cả register và boot:
<?php
namespace Vietiso\Core\Database;
use Vietiso\Core\Container\ServiceProvider;
class DatabaseServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton('database', fn ($app) => new ConnectionManager(
$app->get('pool')
));
$this->app->alias('database', ConnectionManagerInterface::class);
}
public function boot(): void
{
// Setup Model events (cần EventDispatcher đã register)
Model::setEventDispatcher($this->app->get('event_dispatcher'));
// Load database connections từ config
$database = $this->app->get('database');
foreach (config('database.connections') as $name => $config) {
$database->addConnection($name, $config);
}
$database->setDefaultConnection(config('database.default'));
}
}
Provider với nhiều Services
Một provider có thể đăng ký nhiều services liên quan:
<?php
namespace Vietiso\Core\Event;
use Vietiso\Core\Container\ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
public function register(): void
{
// Event Listener Provider
$this->app->singleton('events', fn () => new EventListenerProvider());
$this->app->alias('events', EventListenerProviderInterface::class);
// Event Dispatcher (phụ thuộc vào events)
$this->app->singleton('event_dispatcher', fn () => new EventDispatcher(
$this->app->get('events')
));
$this->app->alias('event_dispatcher', EventDispatcherInterface::class);
}
}
Provider với các Binding Types khác nhau
<?php
namespace Vietiso\Core\Mail;
use Vietiso\Core\Container\ServiceProvider;
class MailServiceProvider extends ServiceProvider
{
public function register(): void
{
// Singleton: factory dùng chung
$this->app->singleton('mailer_factory', fn ($app) => new MailerFactory(
$app->get('config')
));
// Transient: mỗi lần resolve tạo mailer mới
$this->app->transient('mailer', fn ($app) => $app->get('mailer_factory')->driver());
$this->app->alias('mailer', MailerInterface::class);
}
}
Đăng ký Service Providers
Core Providers
Framework providers được đăng ký trong core/providers.php:
<?php
// core/providers.php
return [
Vietiso\Core\Environment\EnvironmentServiceProvider::class,
Vietiso\Core\Config\ConfigServiceProvider::class,
Vietiso\Core\Translation\TranslationServiceProvider::class,
Vietiso\Core\Log\LogServiceProvider::class,
Vietiso\Core\Event\EventServiceProvider::class,
Vietiso\Core\Database\DatabaseServiceProvider::class,
Vietiso\Core\Cache\CacheServiceProvider::class,
Vietiso\Core\Mail\MailServiceProvider::class,
// ... 33 providers
];
Module Providers
Mỗi module đăng ký providers riêng trong file .module.php:
<?php
// modules/booking/booking.module.php
return [
'name' => 'Booking',
'description' => 'Booking management module',
'providers' => [
Vietiso\Modules\Booking\Providers\BookingServiceProvider::class,
Vietiso\Modules\Booking\Providers\BookingEventProvider::class,
],
];
Đăng ký thêm Providers
Đăng ký providers bổ sung trong file vietiso:
App::getInstance()
->setBasePath(__DIR__)
->withProviders(static function () {
return [
App\Providers\PaymentServiceProvider::class,
App\Providers\SearchServiceProvider::class,
];
})
->start();
Thứ tự Boot
Providers được boot theo thứ tự đăng ký trong providers.php. Điều này quan trọng vì một số providers phụ thuộc vào providers khác:
1. EnvironmentServiceProvider → Load .env (không phụ thuộc)
2. ConfigServiceProvider → Load config files (cần Environment)
3. LogServiceProvider → Setup logging (cần Config)
4. EventServiceProvider → Event system (cần Config)
5. DatabaseServiceProvider → Database (cần Config, Pool, Event)
...
Nếu provider A phụ thuộc vào service từ provider B, hãy đảm bảo provider B được đăng ký trước provider A trong providers.php.
Quá trình Boot chi tiết
ServiceProviderManager
│
├── Phase 1: Register
│ ├── new ProviderA($app) → providerA->register()
│ ├── new ProviderB($app) → providerB->register()
│ └── new ProviderC($app) → providerC->register()
│
└── Phase 2: Boot
├── providerA->boot() → cleanup
├── providerB->boot() → cleanup
└── providerC->boot() → cleanup
ServiceProviderManager đảm bảo:
- Không duplicate providers (kiểm tra
is_subclass_of) - Providers được cleanup sau khi boot (giải phóng memory)
- Boot có thể selective (boot một provider cụ thể)