Gacela
Documentation version: 2.3.0
Colour theme

Docs Core concepts

On this page

Provider

The Provider handles cross-module dependencies. When your module needs something from another module, the Provider is where you wire that connection, always through the other module's Facade.

Start from the consuming service

Let the service constructor show that the Sales module needs the Comment module. The Factory asks for the other module's Facade interface; it does not decide how to locate it.

src/Sales/SalesFactory.phpphp
<?php

declare(strict_types=1);

namespace App\Sales;

use App\Comment\CommentFacadeInterface;
use Gacela\Framework\AbstractFactory;

final class SalesFactory extends AbstractFactory
{
    public function createOrderCommentSaver(): OrderCommentSaver
    {
        return new OrderCommentSaver(
            $this->getProvidedDependency(CommentFacadeInterface::class),
        );
    }
}

Satisfy the boundary in the Provider

Now connect that interface to the Comment module's Facade. #[Provides] keeps the dependency local to the Sales module and resolves it lazily.

src/Sales/SalesProvider.phpphp
<?php

declare(strict_types=1);

namespace App\Sales;

use App\Comment\CommentFacade;
use App\Comment\CommentFacadeInterface;
use Gacela\Framework\AbstractProvider;
use Gacela\Framework\Attribute\Provides;
use Gacela\Framework\Container\Container;

final class SalesProvider extends AbstractProvider
{
    #[Provides(CommentFacadeInterface::class)]
    public function commentFacade(Container $container): CommentFacadeInterface
    {
        return $container->getLocator()->getRequired(CommentFacade::class);
    }
}

Complete call path

The caller still sees only the Sales Facade. The dependency becomes visible only when following the implementation inward: Facade → Factory → Provider → Comment Facade.

<?php # src/Sales/SalesFacade.php

namespace App\Sales;

use Gacela\Framework\AbstractFacade;

/**
 * @method SalesFactory getFactory()
 */
final class SalesFacade extends AbstractFacade
{
    public function saveComment(Comment $comment): int
    {
        return $this->getFactory()
            ->createOrderCommentSaver()
            ->save($comment);
    }
}

More #[Provides] patterns

#[Provides] also accepts string IDs and non-Facade services. Each method is wrapped in a lazy closure and receives Container automatically when declared in the signature.

<?php # src/Sales/SalesProvider.php

use Gacela\Framework\AbstractProvider;
use Gacela\Framework\Attribute\Provides;
use Gacela\Framework\Container\Container;

final class SalesProvider extends AbstractProvider
{
    #[Provides('COMMANDS')]
    public function commands(): array
    {
        return [new SyncCommand()];
    }

    #[Provides('FACADE_COMMENT')]
    public function commentFacade(Container $container): CommentFacade
    {
        return $container->getLocator()->get(CommentFacade::class);
    }
}

With #[Provides], provideModuleDependencies() becomes non-abstract. Providers can go attribute-only or mix both styles.

Mixing with provideModuleDependencies()

You can use attributes alongside the traditional method. Attribute-registered services are resolved first, then provideModuleDependencies() runs as before:

final class SalesProvider extends AbstractProvider
{
    #[Provides('COMMANDS')]
    public function commands(): array
    {
        return [new SyncCommand()];
    }

    public function provideModuleDependencies(Container $container): void
    {
        $container->set('LEGACY_SERVICE', fn () => new LegacyAdapter());
    }
}