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.
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.
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.
O arquivo examples/beginner.php demonstra set(), singleton(), factory(), autowire(), alias(), lazy(), get(), has(), make() e call().
composer install
php examples/beginner.phpImportant
O builder configura; o container resolve. Autowiring é explícito, interfaces não são adivinhadas e escalares nunca são inventados.
- Recursos e instalação
- Início rápido
- Ciclos de vida
- Values, singletons, factories e aliases
- Autowiring
make()ecall()- Lazy services
- Referência da API pública
- Exceções e PSR-11
- Workers, segurança e limitações
- Desenvolvimento
- 📦 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 ecall()para invocação; - 💤 proxies nativos via
omegaalfa/lazy-object; - 🔄 ciclos detectados com caminho completo;
- ✅
Psr\Container\ContainerInterface.
Requer PHP ^8.4, Composer 2 e psr/container ^2.0.
composer require omegaalfa/containerPara lazy services:
composer require omegaalfa/lazy-objectCheckout local:
composer install
php exemplo.php<?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| 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.
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');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.
Executa em cada resolução e não armazena resultado.
$builder->factory(
RequestContext::class,
static fn (ContainerInterface $_container): RequestContext => new RequestContext(),
);$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.
$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
nullsem 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() 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() 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 realComportamento:
- registro,
build()ehas()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.
| 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.
| 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çã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.
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.
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.
composer install
composer test
composer analyse
composer lint
composer check
XDEBUG_MODE=coverage composer coverage
composer benchmark
php exemplo.phpO benchmark usa hrtime(true), warm-up e várias rodadas. Resultados dependem de hardware, OPcache, JIT e carga.
Distribuído sob a licença MIT.