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:

  1. Registers services into the container
  2. 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