Gacela
Documentation version: 2.7.1
Colour theme

Docs Core concepts

On this page

Factory

The Factory (opens in a new tab) creates the internal objects of your module and wires their dependencies: values from Config, services from the Provider.

Start from the object you need

Once a Facade delegates an operation, design the service that carries it out. Its constructor names the collaborators it needs:

src/Comment/Domain/SpamChecker.phpphp
<?php

declare(strict_types=1);

namespace App\Comment\Domain;

use Symfony\Contracts\HttpClient\HttpClientInterface;

final class SpamChecker
{
    public function __construct(
        private HttpClientInterface $client,
        private string $endpoint,
    ) {}

    public function getSpamScore(string $comment): int
    {
        // Business logic using $this->client and $this->endpoint.
        return 0;
    }
}

Construct it in the Factory

Now make the Factory satisfy that constructor. Configuration and wiring stay here, out of the service and the Facade.

src/Comment/CommentFactory.phpphp
<?php

declare(strict_types=1);

namespace App\Comment;

use App\Comment\Domain\SpamChecker;
use Gacela\Framework\AbstractFactory;
use Symfony\Contracts\HttpClient\HttpClient;

/**
 * @extends AbstractFactory<CommentConfig>
 */
final class CommentFactory extends AbstractFactory
{
    public function createSpamChecker(): SpamChecker
    {
        return new SpamChecker(
            HttpClient::create(),
            $this->getConfig()->getSpamCheckerEndpoint(),
        );
    }
}

View the complete Factory (opens in a new tab).

Auto-wiring dependencies into the Factory

Gacela auto-wires the Factory's constructor dependencies. It instantiates concrete classes for you, resolving their own dependencies recursively. For an interface, define a binding to tell Gacela which implementation to use:

<?php # gacela.php

return function (GacelaConfig $config) {
    // Class binding: Gacela instantiates Concrete (and auto-wires its deps)
    $config->addBinding(InterfaceToConcrete::class, Concrete::class);

    // Callable binding: lazy-loaded, you control the instantiation
    $config->addBinding(InterfaceToCallable::class, fn() => new Concrete());
};

The two styles differ:

  • Class binding (Concrete::class): Gacela creates a new instance on the fly and auto-wires its constructor dependencies recursively
  • Callable binding (fn() => ...): you control instantiation. The closure is lazy: it runs only when the dependency is needed

Real example: symfony-gacela-example/gacela.php (opens in a new tab)

To choose the implementation per parameter instead, use the #[Inject] attribute. When more than one source could satisfy a parameter, the container follows a fixed resolution order.

Sharing a single instance

A plain create...() method builds a fresh object on every call. To build a dependency once and reuse it, use singleton():

protected function singleton(string $key, callable $creator): mixed;

It stores the result of $creator under $key and returns the same instance on every later call within the module. The creator is lazy: it runs only on first access.

<?php # src/Comment/CommentFactory.php

final class CommentFactory extends AbstractFactory
{
    public function createSpamChecker(): SpamChecker
    {
        return $this->singleton(
            SpamChecker::class,
            fn (): SpamChecker => new SpamChecker(
                HttpClient::create(),
                $this->getConfig()->getSpamCheckerEndpoint(),
            ),
        );
    }
}