Service Providers
Service providers are the central place for configuring and bootstrapping your plugin's services.
What is a Service Provider?
A service provider is a class that:
- Registers services into the container
- Boots services after all registration is complete
<?php
namespace Vendor\MyPlugin\Providers;
use WPZylos\Framework\Core\ServiceProvider;
use Vendor\MyPlugin\Services\PaymentGateway;
use Vendor\MyPlugin\Contracts\PaymentInterface;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind services to container
$this->container->singleton(PaymentInterface::class, PaymentGateway::class);
}
public function boot(): void
{
// Services are now available
$gateway = $this->container->get(PaymentInterface::class);
$gateway->initialize();
}
}
Provider Lifecycle
sequenceDiagram
participant App as Application
participant P1 as Provider A
participant P2 as Provider B
participant Container
Note over App: Registration Phase
App->>P1: register()
P1->>Container: bind services
App->>P2: register()
P2->>Container: bind services
Note over App: Boot Phase
App->>P1: boot()
P1->>Container: get services
App->>P2: boot()
P2->>Container: get services
Creating a Service Provider
Basic Provider
<?php
namespace Vendor\MyPlugin\Providers;
use WPZylos\Framework\Core\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// Register bindings
}
public function boot(): void
{
// Bootstrap services
}
}
Available Properties
class MyProvider extends ServiceProvider
{
// Access plugin context
protected ContextInterface $context;
// Access container
protected Container $container;
public function register(): void
{
$prefix = $this->context->prefix();
$this->container->bind(...);
}
}
Registering Providers
In config/app.php
<?php
return [
'providers' => [
\Vendor\MyPlugin\Providers\AppServiceProvider::class,
\Vendor\MyPlugin\Providers\RouteServiceProvider::class,
\Vendor\MyPlugin\Providers\DatabaseServiceProvider::class,
],
];
Programmatically
$app->registerProvider(new CustomProvider($context, $container));
Common Provider Patterns
Route Service Provider
<?php
namespace Vendor\MyPlugin\Providers;
use WPZylos\Framework\Core\ServiceProvider;
use WPZylos\Framework\Routing\Router;
class RouteServiceProvider extends ServiceProvider
{
public function boot(): void
{
$router = $this->container->get(Router::class);
// Load route files
$this->loadRoutes($router);
}
private function loadRoutes(Router $router): void
{
$routesPath = $this->context->path('routes');
if (is_admin()) {
require $routesPath . '/admin.php';
}
require $routesPath . '/ajax.php';
}
}
Admin Service Provider
<?php
namespace Vendor\MyPlugin\Providers;
use WPZylos\Framework\Core\ServiceProvider;
use WPZylos\Framework\Hooks\HookManager;
class AdminServiceProvider extends ServiceProvider
{
public function boot(): void
{
if (!is_admin()) {
return;
}
$hooks = $this->container->get(HookManager::class);
$hooks->wpAction('admin_menu', [$this, 'registerMenus']);
$hooks->wpAction('admin_init', [$this, 'registerSettings']);
}
public function registerMenus(): void
{
add_menu_page(
__('My Plugin', $this->context->textDomain()),
__('My Plugin', $this->context->textDomain()),
'manage_options',
$this->context->slug(),
[$this, 'renderDashboard'],
'dashicons-admin-plugins',
30
);
}
public function renderDashboard(): void
{
$view = $this->container->get(ViewFactory::class);
echo $view->render('admin/dashboard');
}
}
Database Service Provider
<?php
namespace Vendor\MyPlugin\Providers;
use WPZylos\Framework\Core\ServiceProvider;
use WPZylos\Framework\Database\Connection;
class DatabaseServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->container->singleton(Connection::class, function () {
return new Connection($this->context);
});
}
}
Deferred Providers
For performance, providers can defer registration until needed:
<?php
namespace Vendor\MyPlugin\Providers;
use WPZylos\Framework\Core\ServiceProvider;
use WPZylos\Framework\Core\Contracts\DeferrableProvider;
class HeavyServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function provides(): array
{
return [
HeavyService::class,
AnotherHeavyService::class,
];
}
public function register(): void
{
// Only called when HeavyService is first requested
$this->container->singleton(HeavyService::class, ...);
}
}
Best Practices
Good: Keep register() Quick
public function register(): void
{
// Good: Good: Just bind, don't resolve
$this->container->singleton(Logger::class, FileLogger::class);
}
Bad: Don't Resolve in register()
public function register(): void
{
// Bad: Bad: Resolving other services
$config = $this->container->get(Config::class);
// Other providers may not have registered Config yet!
}
Good: Use boot() for Cross-Service Logic
public function boot(): void
{
// Good: Good: All services are registered
$config = $this->container->get(Config::class);
$logger = $this->container->get(Logger::class);
if ($config->get('logging.enabled')) {
$logger->enable();
}
}
Good: Check Context Before Admin-Only Work
public function boot(): void
{
// Good: Good: Skip unnecessary work
if (!is_admin()) {
return;
}
$this->registerAdminHooks();
}
Testing Providers
public function testProviderRegistersServices(): void
{
$context = $this->createMock(ContextInterface::class);
$container = new Container();
$provider = new DatabaseServiceProvider($context, $container);
$provider->register();
$this->assertTrue($container->has(Connection::class));
}
Next Steps
- Request Lifecycle — When providers run
- Hook System — Register hooks in boot()