Skip to content

Repository files navigation

🧩 Omegaalfa Container

Dependency Injection PSR-11 explícito e tipado para PHP 8.4

PHP 8.4+ PSR-11 PHPStan max License MIT

O problema que este pacote resolve

Em uma aplicação pequena, montar objetos manualmente é simples:

$config = new Config(...);
$logger = new Logger(...);
$database = new Database($config, $logger);
$repository = new UserRepository($database);
$service = new UserService($repository, $logger);
$controller = new UserController($service);

Conforme a aplicação cresce, essa montagem se repete. A ordem fica difícil de controlar, trocar implementações exige mudanças em vários arquivos, algumas instâncias precisam ser compartilhadas e outras precisam ser novas. Testes também passam a conhecer detalhes de infraestrutura.

Com o container, as regras ficam centralizadas:

use Omegaalfa\Container\ContainerBuilder;
use Psr\Container\ContainerInterface;

$builder = new ContainerBuilder();
$builder->set(Config::class, new Config(...));
$builder->singleton(Logger::class, static fn(): Logger => new Logger());
$builder->singleton(
    Database::class,
    static fn(ContainerInterface $container): Database => new Database(
        $container->get(Config::class),
        $container->get(Logger::class),
    ),
);
$builder->autowire(UserRepository::class);
$builder->autowire(UserService::class);
$builder->autowire(UserController::class);
$container = $builder->build();
$controller = $container->get(UserController::class);

Em vez de cada parte da aplicação decidir como criar suas dependências, essa responsabilidade fica concentrada na configuração do container.

O que é injeção de dependências?

Sem injeção, uma classe cria internamente aquilo de que precisa:

final class UserService
{
    public function __construct()
    {
        $database = new Database();
        $this->repository = new UserRepository($database);
    }
}

Com injeção, a classe recebe a dependência:

final class UserService
{
    public function __construct(private UserRepository $repository)
    {
    }
}

Dependency Injection é uma técnica de projeto. O container é uma ferramenta que ajuda a aplicá-la; eles não são a mesma coisa.

ContainerBuilder e Container: qual é a diferença?

O builder funciona como a receita; o container é a estrutura pronta.

Objeto Responsabilidade
ContainerBuilder Registrar e configurar serviços
Container Resolver e devolver serviços durante a execução
build() Criar um snapshot da configuração atual

Snapshot é uma cópia naquele momento. Alterar o builder depois de build() não altera containers já construídos. Cada container mantém seus próprios singletons e proxies lazy.

Exemplo completo para iniciantes

O arquivo examples/beginner.php demonstra set(), singleton(), factory(), autowire(), alias(), lazy(), get(), has(), make() e call().

composer install
php examples/beginner.php

Important

O builder configura; o container resolve. Autowiring é explícito, interfaces não são adivinhadas e escalares nunca são inventados.

📑 Índice

  • Recursos e instalação
  • Início rápido
  • Ciclos de vida
  • Values, singletons, factories e aliases
  • Autowiring
  • make() e call()
  • Lazy services
  • Referência da API pública
  • Exceções e PSR-11
  • Workers, segurança e limitações
  • Desenvolvimento

✨ Recursos

  • 📦 values prontos: escalares, objetos e closures;
  • ♻️ singletons lazy por container;
  • 🏭 factories transient;
  • 🔗 aliases que preservam o ciclo de vida;
  • 🧠 autowiring explícito por construtor e cache de Reflection;
  • 🧰 make() para criação transient e call() para invocação;
  • 💤 proxies nativos via omegaalfa/lazy-object;
  • 🔄 ciclos detectados com caminho completo;
  • Psr\Container\ContainerInterface.

📋 Instalação

Requer PHP ^8.4, Composer 2 e psr/container ^2.0.

composer require omegaalfa/container

Para lazy services:

composer require omegaalfa/lazy-object

Checkout local:

composer install
php exemplo.php

🚀 Início rápido

<?php

declare(strict_types=1);

use Omegaalfa\Container\ContainerBuilder;
use Psr\Container\ContainerInterface;

$builder = new ContainerBuilder();
$builder->set('app.name', 'Minha aplicação');
$builder->set(Config::class, new Config('production'));
$builder->singleton(
    Logger::class,
    static fn (ContainerInterface $_container): Logger => new Logger(),
);
$builder->alias(LoggerInterface::class, Logger::class);
$builder->autowire(UserRepository::class);
$builder->autowire(UserService::class);

$container = $builder->build();
$service = $container->get(UserService::class);

O arquivo exemplo.php é executável e cobre todos os recursos principais:

php exemplo.php

🔁 Ciclos de vida

Registro Execução Cache Identidade
set() valor já pronto definição sempre a mesma
singleton() primeiro get() resultado a mesma por container
factory() todo get() nenhum normalmente diferente
autowire() todo get() nenhum diferente
lazy() proxy no primeiro get(); real no acesso proxy mesmo proxy
alias() segue destino segue destino segue destino
make() toda chamada nenhum diferente

Containers de chamadas distintas a build() não compartilham singletons, proxies ou estado de resolução.

📦 Values, singletons, factories e aliases

Value

set() devolve exatamente o valor registrado. Closure registrada assim não é executada.

$builder->set('app.name', 'Omegaalfa');
$builder->set('limit', 100);
$builder->set(LoggerInterface::class, new ConsoleLogger());
$builder->set('callback', static fn (): string => 'valor');

Singleton

A factory executa uma vez, somente no primeiro get().

$builder->singleton(
    DatabaseConnection::class,
    static fn (ContainerInterface $container): DatabaseConnection =>
        new DatabaseConnection($container->get(Config::class)->dsn),
);

Falhas não armazenam resultados parciais. A exceção original é preservada e outra resolução tenta novamente.

Factory transient

Executa em cada resolução e não armazena resultado.

$builder->factory(
    RequestContext::class,
    static fn (ContainerInterface $_container): RequestContext => new RequestContext(),
);

Alias

$builder->singleton(RedisCache::class, static fn (): RedisCache => new RedisCache());
$builder->alias(CacheInterface::class, RedisCache::class);

O alias preserva o ciclo de vida do destino. Self-alias é rejeitado. Destino ausente ou cadeia circular faz has() retornar false.

Note

O container pode ser usado em factories na composition root. Evite injetá-lo nos serviços como Service Locator.

🧠 Autowiring

$builder->autowire(UserRepository::class);
$builder->autowire(UserService::class);

Política:

  • resolve tipos de objeto registrados;
  • interfaces exigem registro ou alias explícito;
  • usa defaults quando não há resolução;
  • nullable recebe null sem candidato;
  • escalares obrigatórios e parâmetros sem tipo não são inventados;
  • union exige exatamente um candidato;
  • intersection é rejeitada;
  • classes abstratas, inexistentes e construtores não públicos são rejeitados;
  • variádicos sem valores explícitos ficam vazios;
  • nenhuma propriedade é injetada após a construção.
final readonly class UserService
{
    public function __construct(
        public UserRepository $repository,
        public int $limit = 25,
    ) {}
}

🧰 make() e call()

make() cria objeto transient, resolve dependências e aceita parâmetros por nome:

$controller = $container->make(
    UserController::class,
    ['request' => $request, 'limit' => 50],
);

Não modifica eventual singleton registrado. Parâmetro desconhecido ou obrigatório ausente gera UnresolvableParameterException.

call() resolve closures, funções e métodos:

$result = $container->call(
    [$controller, 'handle'],
    ['request' => $request],
);
$result = $container->call(
    static fn (LoggerInterface $logger, string $message): string =>
        $handler->handle($logger, $message),
    ['message' => 'Processar pedido'],
);

O retorno não é transformado. Use somente callables confiáveis.

💤 Lazy services

lazy() chama Omegaalfa\LazyObject\LazyObject::proxy(); não há proxy próprio.

$builder->lazy(
    ReportGenerator::class,
    static fn (ContainerInterface $container): ReportGenerator =>
        new ReportGenerator($container->get(DatabaseConnection::class)),
    ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE,
);

$container = $builder->build();
$report = $container->get(ReportGenerator::class); // proxy
echo $report->status; // inicializa a instância real

Comportamento:

  • registro, build() e has() não executam a factory;
  • o primeiro get() cria um proxy ainda não inicializado;
  • o proxy é singleton por container;
  • a factory captura o container, nunca o builder;
  • opções nativas são encaminhadas sem alteração;
  • pacote ausente gera LazyServiceException, sem fallback eager;
  • erros da biblioteca e do PHP são preservados;
  • classes sem propriedades podem não permanecer lazy.

Lazy loading adia criação; não substitui Dependency Injection.

📚 Referência da API pública

ContainerBuilder

Assinatura Descrição
set(string $id, mixed $value, bool $override = false): self Registra valor pronto.
singleton(string $id, Closure $factory, bool $override = false): self Factory compartilhada.
factory(string $id, Closure $factory, bool $override = false): self Factory transient.
alias(string $id, string $target, bool $override = false): self Identificador alternativo.
autowire(string $class, bool $override = false): self Construção explícita por Reflection.
lazy(string $class, Closure $factory, int $options = 0, bool $override = false): self Proxy lazy singleton.
addDefinitions(array $definitions, bool $override = false): self Objetos de definição; API de baixo nível.
build(): Container Snapshot independente das definições.

Identificador vazio é rejeitado. Duplicatas exigem override: true. Containers já construídos não mudam quando o builder é alterado.

Container

Assinatura Descrição
get(string $id): mixed Resolve entrada conforme o ciclo de vida.
has(string $id): bool Verifica sem construir ou executar factory.
make(string $class, array $parameters = []): object Cria instância transient.
call(callable $callable, array $parameters = []): mixed Injeta argumentos e invoca callable.

has() === true indica definição conhecida, não que a construção futura será infalível. Prefira ContainerBuilder::build() ao construtor direto de Container.

🚨 Exceções

Exceção Situação
ContainerException base PSR-11
NotFoundException entrada ausente; implementa NotFoundExceptionInterface
CircularDependencyException ciclo em alias, factory ou autowiring
InvalidDefinitionException id vazio, duplicata ou definição inválida
AutowiringException classe não construível
AmbiguousTypeException union ambígua
UnresolvableParameterException parâmetro não resolvível
LazyServiceException pacote lazy ausente
Circular dependency detected: A -> B -> C -> A

Exceções de factories, construtores e callables consumidores são preservadas.

🔌 PSR-11

Container implementa Psr\Container\ContainerInterface. A PSR-11 padroniza apenas get() e has(); registros são extensões do pacote.

function boot(Psr\Container\ContainerInterface $container): void
{
    if ($container->has(Application::class)) {
        $application = $container->get(Application::class);
    }
}

O Composer fornece psr/container-implementation: 1.0.0, conforme a PSR-11. Isso é independente de psr/container ^2.0.

🧵 Workers, segurança e limitações

Não existe estado estático global. Singletons vivem enquanto o container; em workers persistentes, reusar o container também mantém singletons e proxies. Não há escopo automático por request, Fiber, coroutine ou tenant.

Identificadores, classes, factories e callables são configuração executável confiável. Não encaminhe entrada HTTP diretamente a get(), make() ou call() e não carregue definições remotas.

Ainda não existem bindings contextuais, atributos, property injection, autowiring implícito por get(), lazy ghosts, lazy transient, compilação ou escopos complexos. Consulte ROADMAP.md.

🧪 Desenvolvimento

composer install
composer test
composer analyse
composer lint
composer check
XDEBUG_MODE=coverage composer coverage
composer benchmark
php exemplo.php

O benchmark usa hrtime(true), warm-up e várias rodadas. Resultados dependem de hardware, OPcache, JIT e carga.

📄 Licença

Distribuído sob a licença MIT.

About

Container de Dependency Injection

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages