Advanced

Service Providers

Organize application services and bootstrapping

Service providers are the central place to configure and bootstrap your application services.

Quick Start

Create a Service Provider

app/Providers/AppServiceProvider.php:

<?php

namespace App\Providers;

use Glueful\Container\Providers\BaseServiceProvider;
use Glueful\Container\Definition\FactoryDefinition;
use Glueful\Container\Definition\AliasDefinition;

final class AppServiceProvider extends BaseServiceProvider
{
    public function defs(): array
    {
        return [
            // Autowire concrete service
            App\Services\OrderService::class => $this->autowire(App\Services\OrderService::class),

            // Factory with config
            'payment.gateway' => new FactoryDefinition(
                'payment.gateway',
                fn() => new App\Payments\StripePaymentGateway(config('services.stripe'))
            ),

            // Alias interface to id
            App\Contracts\PaymentGatewayInterface::class =>
                new AliasDefinition(App\Contracts\PaymentGatewayInterface::class, 'payment.gateway'),
        ];
    }
}

Register Provider

Register providers in config/serviceproviders.php:

return [
    'enabled' => [
        App\Providers\AppServiceProvider::class,
    ],
];

Provider Model

  • Providers extend BaseServiceProvider and return an array of service definitions in defs().
  • Use autowire() for concrete classes, FactoryDefinition for factory-built services, and AliasDefinition to map type-hints to ids.
  • Avoid side effects in providers; initialize on first use or via explicit boot steps (e.g., console commands, listeners) rather than a boot() method.

Built-in Providers

Glueful ships with several providers you can inspect for reference:

  • CoreProvider — core services/aliases
    • File: src/Container/Providers/CoreProvider.php
    • Aliases/services: 'logger' (→ Psr\Log\LoggerInterface), 'database', 'request', 'cache.store' (via factory), query/schema builders
  • RepositoryProvider — repositories factory
    • File: src/Container/Providers/RepositoryProvider.php
    • Services: Glueful\Repository\RepositoryFactory, alias 'repository'
  • QueueProvider — queue system
    • File: src/Queue/ServiceProvider/QueueProvider.php
    • Services: Glueful\Queue\QueueManager, Glueful\Queue\Failed\FailedJobProvider, Glueful\Scheduler\JobScheduler
  • HttpClientProvider — HTTP client stack
    • File: src/Http/ServiceProvider/HttpClientProvider.php
    • Services: Symfony\Contracts\HttpClient\HttpClientInterface, Psr\Http\Client\ClientInterface, Glueful\Http\Client
  • SerializerProvider — serialization/normalization
    • File: src/Serialization/ServiceProvider/SerializerProvider.php
  • SecurityProvider — auth/security helpers
    • File: src/Security/ServiceProvider/SecurityProvider.php
  • TasksProvider — task scheduling/related bindings
    • File: src/Tasks/ServiceProvider/TasksProvider.php
  • (PSR‑15) HttpPsr15Provider — PSR‑15 bridge config
    • File: src/Container/Providers/HttpPsr15Provider.php

Tip: Open these files to learn consistent patterns for FactoryDefinition, AliasDefinition, and autowire usage.

Common Patterns

Database Service Example

CoreProvider registers a connection as 'database'. To expose a scoped helper:

use Glueful\Container\Providers\BaseServiceProvider;
use Glueful\Container\Definition\FactoryDefinition;

final class ReportingDbProvider extends BaseServiceProvider
{
    public function defs(): array
    {
        return [
            'db.reporting' => new FactoryDefinition(
                'db.reporting',
                fn(\Psr\Container\ContainerInterface $c) => $c->get('database')
            ),
        ];
    }
}

Cache Service Example

Glueful registers 'cache.store' and aliases Glueful\Cache\CacheStore::class. To create a named cache:

use Glueful\Container\Providers\BaseServiceProvider;
use Glueful\Container\Definition\FactoryDefinition;

final class CacheServiceProvider extends BaseServiceProvider
{
    public function defs(): array
    {
        return [
            'cache.reports' => new FactoryDefinition(
                'cache.reports',
                fn() => \Glueful\Cache\CacheFactory::create()
            ),
        ];
    }
}

Queue Service Example

Resolve the manager as needed:

$queue = app(\Glueful\Queue\QueueManager::class);
$queue->push(App\Jobs\SendEmail::class, ['userId' => $id]);

Event Service Provider

class EventServiceProvider
{
    public function register(Container $container): void
    {
        $container->singleton(EventDispatcher::class);
    }

    public function boot(Container $container): void
    {
        $events = $container->make(EventDispatcher::class);

        // Register event listeners
        $events->listen(UserRegistered::class, SendWelcomeEmail::class);
        $events->listen(UserRegistered::class, CreateUserProfile::class);
        $events->listen(OrderCreated::class, SendOrderConfirmation::class);
        $events->listen(OrderCreated::class, UpdateInventory::class);
    }
}

Notification Service Provider

class NotificationServiceProvider
{
    public function register(Container $container): void
    {
        $container->singleton('notifications', function ($app) {
            $manager = new NotificationManager();

            // Register channels
            $manager->registerChannel('email', new EmailChannel(
                $app['config']->get('mail')
            ));

            $manager->registerChannel('sms', new SmsChannel(
                $app['config']->get('sms')
            ));

            $manager->registerChannel('push', new PushChannel(
                $app['config']->get('push')
            ));

            return $manager;
        });
    }
}

Custom Providers

Repository Provider

class RepositoryServiceProvider
{
    public function register(Container $container): void
    {
        // Bind repository interfaces
        $container->bind(
            UserRepositoryInterface::class,
            UserRepository::class
        );

        $container->bind(
            OrderRepositoryInterface::class,
            OrderRepository::class
        );

        $container->bind(
            ProductRepositoryInterface::class,
            ProductRepository::class
        );
    }
}

API Client Provider

class ApiServiceProvider
{
    public function register(Container $container): void
    {
        // Stripe
        $container->singleton(StripeClient::class, function ($app) {
            return new StripeClient(
                $app['config']->get('services.stripe.secret')
            );
        });

        // SendGrid
        $container->singleton(SendGridClient::class, function ($app) {
            return new SendGridClient(
                $app['config']->get('services.sendgrid.api_key')
            );
        });

        // AWS S3
        $container->singleton(S3Client::class, function ($app) {
            $config = $app['config']->get('filesystems.s3');

            return new S3Client([
                'credentials' => [
                    'key' => $config['key'],
                    'secret' => $config['secret'],
                ],
                'region' => $config['region'],
                'version' => 'latest',
            ]);
        });
    }
}

Validation Provider

class ValidationServiceProvider
{
    public function boot(Container $container): void
    {
        $validator = $container->make(Validator::class);

        // Register custom rules
        $validator->extend('phone', function ($value) {
            return preg_match('/^\+?[1-9]\d{1,14}$/', $value);
        });

        $validator->extend('strong_password', function ($value) {
            return strlen($value) >= 8
                && preg_match('/[A-Z]/', $value)
                && preg_match('/[a-z]/', $value)
                && preg_match('/[0-9]/', $value);
        });
    }
}

Advanced Patterns

Deferred Providers

Load providers only when needed:

class ImageServiceProvider
{
    public array $provides = [
        ImageProcessor::class,
        'image',
    ];

    public function register(Container $container): void
    {
        $container->singleton('image', function ($app) {
            return new ImageProcessor(
                $app['config']->get('image')
            );
        });
    }

    public function isDeferred(): bool
    {
        return true;
    }
}

Conditional Registration

public function register(Container $container): void
{
    // Only in production
    if ($container['config']->get('app.env') === 'production') {
        $container->singleton(ErrorTracker::class, SentryTracker::class);
    }

    // Only in development
    if ($container['config']->get('app.debug')) {
        $container->singleton(DebugBar::class);
    }
}

Environment-Specific Services

public function register(Container $container): void
{
    $env = $container['config']->get('app.env');

    $container->singleton(PaymentGateway::class, match ($env) {
        'production' => StripePaymentGateway::class,
        'staging' => StripeTestGateway::class,
        default => FakePaymentGateway::class,
    });
}

Testing with Providers

Override Bindings in Tests

class UserServiceTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // Replace provider bindings
        app()->bind(EmailService::class, FakeEmailService::class);
        app()->bind(PaymentGateway::class, FakePaymentGateway::class);
    }

    public function test_creates_user()
    {
        $service = app(UserService::class);
        $user = $service->create(['email' => '[email protected]']);

        $this->assertNotNull($user);
    }
}

Mock Services

class OrderServiceTest extends TestCase
{
    public function test_processes_order()
    {
        // Mock payment gateway
        $gateway = $this->createMock(PaymentGateway::class);
        $gateway->method('charge')->willReturn(['status' => 'success']);

        app()->instance(PaymentGateway::class, $gateway);

        // Test
        $service = app(OrderService::class);
        $order = $service->processOrder($orderData);

        $this->assertEquals('completed', $order->status);
    }
}

Best Practices

1. Keep Providers Focused

// ✅ Good - focused provider
class CacheServiceProvider
{
    public function register(Container $container): void
    {
        // Only cache-related bindings
    }
}

// ❌ Bad - mixed concerns
class AppServiceProvider
{
    public function register(Container $container): void
    {
        // Cache, queue, email, payment, etc.
    }
}

2. Use Boot for Side Effects

// ✅ Good - register in register(), bootstrap in boot()
public function register(Container $container): void
{
    $container->singleton(EventDispatcher::class);
}

public function boot(Container $container): void
{
    $events = $container->make(EventDispatcher::class);
    $events->listen(...);
}

// ❌ Bad - side effects in register()
public function register(Container $container): void
{
    $container->singleton(EventDispatcher::class);
    $events = $container->make(EventDispatcher::class);
    $events->listen(...); // Don't do this in register()
}

3. Document Provider Dependencies

/**
 * Requires:
 * - ConfigServiceProvider
 * - DatabaseServiceProvider
 *
 * Provides:
 * - UserRepository
 * - OrderRepository
 */
class RepositoryServiceProvider
{
    //...
}

4. Organize Providers by Domain

app/Providers/
├── AppServiceProvider.php       # Core app services
├── DatabaseServiceProvider.php  # Database
├── CacheServiceProvider.php     # Caching
├── QueueServiceProvider.php     # Queues
├── EventServiceProvider.php     # Events
├── NotificationServiceProvider.php
├── PaymentServiceProvider.php
└── RepositoryServiceProvider.php

Troubleshooting

Services not available in register()?

  • Move code to boot() method
  • Other providers may not have registered yet

Circular dependency?

  • Review service dependencies
  • Use lazy loading or setter injection
  • Refactor to remove circular reference

Provider not loading?

  • Check config/serviceproviders.php (and config/extensions.php for extensions)
  • Verify namespace/class exists and is autoloaded

Bindings overwritten?

  • Check provider order
  • Last binding wins for same key
  • Use unique service names

Next Steps