Service providers

A provider registers services before the container finalizes its definitions. Each service can have a transient, singleton or scoped lifetime.

Bindings

AppServiceProvider implements ServiceProviderInterface. Its register() method declares scoped ArticleStore and ArticleRepository bindings; the ArticleRepositoryInterface alias points to the same repository definition. Controllers can therefore depend on the interface.

app/Providers/AppServiceProvider.php
<?php

declare(strict_types=1);

namespace App\Providers;

use App\Services\ArticleRepository;
use App\Services\ArticleStore;
use App\Services\ArticleRepositoryInterface;
use Lemonade\Framework\Container\ContainerBuilderInterface;
use Lemonade\Framework\Core\ServiceProviderInterface;

final class AppServiceProvider implements ServiceProviderInterface
{
    public function register(ContainerBuilderInterface $builder): void
    {
        $builder->scoped(ArticleStore::class, ArticleStore::class);
        $builder->scoped(ArticleRepository::class, ArticleRepository::class);
        $builder->alias(ArticleRepositoryInterface::class, ArticleRepository::class);
    }
}

Loading the provider

Providers.yaml adds AppServiceProvider to application bootstrap, and Config.yaml must load the Providers module. Defining the class alone does not register it. register() runs before request handling, so it must not access the current request.

app/Config/Providers.yaml
module: providers
config:
  providers:
    - App\Providers\AppServiceProvider

Lifetimes and consumption

The controller asks for ArticleRepositoryInterface. The container follows the provider alias and resolves controller → repository → store; show() returns the titles through Responses::json(). Here scoped() shares each service within the request; singleton() shares one in the root container, while transient() creates a new instance on each resolution.

app/Controllers/ArticleController.php
<?php

namespace App\Controllers;

use App\Services\ArticleRepositoryInterface;
use Lemonade\Framework\Http\Response\Responses;
use Psr\Http\Message\ResponseInterface;

final readonly class ArticleController
{
    public function __construct(
        private ArticleRepositoryInterface $articles,
        private Responses $responses,
    ) {}

    public function show(): ResponseInterface
    {
        return $this->responses->json(['titles' => $this->articles->titles()]);
    }
}

ArticleRepository takes ArticleStore in its constructor and delegates title lookup to it. Both concrete services are registered by the provider, while the repository implements the interface shown next.

app/Services/ArticleRepository.php
<?php

namespace App\Services;

final class ArticleRepository implements ArticleRepositoryInterface
{
    public function __construct(private readonly ArticleStore $store) {}

    public function titles(): array
    {
        return $this->store->titles();
    }
}

The interface declares the titles() method used by the controller. Its provider alias points to ArticleRepository; without that binding, the controller constructor cannot be resolved.

app/Services/ArticleRepositoryInterface.php
<?php

namespace App\Services;

interface ArticleRepositoryInterface
{
    public function titles(): array;
}

ArticleStore returns two in-memory values so the controller → repository → store path is visible without a database. An application can replace the storage implementation while keeping the controller contract.

app/Services/ArticleStore.php
<?php

namespace App\Services;

final class ArticleStore
{
    public function titles(): array
    {
        return ['First article', 'Second article'];
    }
}
Related documentationRead the Dependency Injection and Service Providers chapter in the documentation for more detail.

← All examples