Nhảy tới nội dung

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: registerboot.

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)
...
Lưu ý thứ tự

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ể)