Skip to content

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.

Factory vs Provider

  • Factory → creates objects inside your module (intra-module)
  • Provider → brings in dependencies from other modules (inter-module)

Setting a provided dependency

php
<?php # src/Sales/SalesProvider.php

use Gacela\Framework\AbstractProvider;

final class SalesProvider extends AbstractProvider
{
    public const FACADE_COMMENT = 'FACADE_COMMENT';

    public function provideModuleDependencies(Container $container): void
    {
        $this->addCommentFacade($container);
    }

    private function addCommentFacade(Container $container): void
    {
        $container->set(
            self::FACADE_COMMENT,
            function (Container $container) {
                return $container->getLocator()->get(CommentFacade::class);
            }
        );
    }
}

Factory using a provided dependency

php
<?php # src/Sales/SalesFactory.php

use Gacela\Framework\AbstractFactory;

/**
 * @method SalesConfig getConfig()
 */
final class SalesFactory extends AbstractFactory
{
    public function createOrderCommentSaver(): FooService
    {
        return new OrderCommentSaver(
            $this->getCommentFacade()
        );
    }

    private function getCommentFacade(): CommentFacade
    {
        return $this->getProvidedDependency(
            SalesProvider::FACADE_COMMENT
        );
    }
}

Putting it together

The Facade calls the Factory, which pulls the provided dependency, completing the chain Facade → Factory → Provider:

php
<?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);
    }
}

#[Provides] attribute

Replace stringly-typed $container->set(KEY, fn () => ...) boilerplate with a declarative method-level attribute.

Instead of overriding provideModuleDependencies(), annotate methods with #[Provides('ID')]. Each method is wrapped in a lazy closure and auto-injected with Container when declared in the signature.

php
<?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:

php
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());
    }
}