Container de Serviços
O PivotPHP inclui um contêiner de serviços simples, compatível com PSR-11 (Psr\Container\ContainerInterface), para registrar e resolver dependências da aplicação. A classe é PivotPHP\Core\Providers\Container e a Application expõe os métodos mais usados diretamente.
Introdução à Injeção de Dependência
Injeção de dependência é uma técnica onde um objeto recebe suas dependências em vez de criá-las. Isso leva a código mais flexível, testável e manutenível.
// Sem injeção de dependência
class UserService
{
public function all(): array
{
$db = new Database(); // Dependência rígida
return $db->query('SELECT * FROM users');
}
}
// Com injeção de dependência
class UserService
{
public function __construct(private Database $db)
{
}
public function all(): array
{
return $this->db->query('SELECT * FROM users');
}
}
API Disponível
Método em $app |
Descrição |
|---|---|
bind(string $abstract, mixed $concrete = null, bool $shared = false) |
Registra um serviço. Por padrão, cria uma nova instância a cada resolução |
singleton(string $abstract, mixed $concrete = null) |
Registra um serviço compartilhado (mesma instância sempre) |
instance(string $abstract, mixed $instance) |
Registra um valor ou objeto já construído |
alias(string $alias, string $abstract) |
Cria um nome alternativo para um serviço |
make(string $abstract) / resolve(string $id) |
Resolve um serviço registrado |
has(string $id) |
Verifica se um serviço está registrado |
getContainer() |
Retorna o contêiner (Providers\Container) |
Os métodos de registro retornam a própria Application, permitindo encadeamento.
Registrando Serviços
Fábricas (closures)
Quando $concrete é uma closure, ela é chamada para construir o serviço e recebe o contêiner como argumento:
use PivotPHP\Core\Providers\Container;
$app->bind(Database::class, function (Container $c) {
return new Database($_ENV['DB_HOST'] ?? 'localhost');
});
$app->bind(UserService::class, function (Container $c) {
return new UserService($c->get(Database::class));
});
bind x singleton
// Nova instância a cada make()
$app->bind('report', fn() => new Report());
$app->make('report') !== $app->make('report'); // true
// Mesma instância sempre
$app->singleton(Cache::class, fn() => new FileCache('/tmp/cache'));
$app->make(Cache::class) === $app->make(Cache::class); // true
// Equivalente a singleton
$app->bind(Cache::class, fn() => new FileCache('/tmp/cache'), true);
Instâncias e valores
$app->instance(ApiClient::class, new ApiClient($_ENV['API_KEY'] ?? ''));
$app->instance('app.timezone', 'America/Sao_Paulo');
Aliases
$app->singleton(Cache::class, fn() => new FileCache('/tmp/cache'));
$app->alias('cache', Cache::class);
$app->make('cache') === $app->make(Cache::class); // true
Interfaces
Para vincular uma interface a uma implementação, use uma closure que construa o objeto:
$app->bind(UserRepositoryInterface::class, fn(Container $c) => new SqlUserRepository(
$c->get(Database::class)
));
Atenção: quando
$concretenão é uma closure, o valor é devolvido como está.$app->bind(UserRepositoryInterface::class, SqlUserRepository::class)fazmake()retornar a string'SqlUserRepository', não uma instância.
Resolvendo Serviços
$service = $app->make(UserService::class);
if ($app->has('cache')) {
$cache = $app->make('cache');
}
// Acesso direto ao contêiner PSR-11
$db = $app->getContainer()->get(Database::class);
O contêiner não faz resolução automática (autowiring): apenas serviços registrados podem ser resolvidos. Resolver um identificador não registrado lança PivotPHP\Core\Exceptions\Container\ServiceNotFoundException (que implementa Psr\Container\NotFoundExceptionInterface). Erros dentro de uma fábrica são relançados como PivotPHP\Core\Exceptions\Container\ContainerException.
Removendo Serviços
Os métodos de remoção estão no contêiner:
$app->getContainer()->forget(Cache::class); // remove um serviço
$app->getContainer()->flush(); // remove todos os serviços e aliases
flush()também remove os serviços internos registrados pelos provedores do framework. Use apenas em testes.
Provedores de Serviço
Organize registros relacionados em provedores de serviço:
namespace App\Providers;
use PivotPHP\Core\Providers\Container;
use PivotPHP\Core\Providers\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Cache::class, fn() => new FileCache('/tmp/cache'));
$this->app->bind(UserRepositoryInterface::class, fn(Container $c) => new SqlUserRepository(
$c->get(Database::class)
));
}
}
Padrões Comuns
Decorator
$app->singleton(Cache::class, function (Container $c) {
$cache = new FileCache('/tmp/cache');
if (($_ENV['APP_ENV'] ?? 'production') === 'local') {
return new LoggingCache($cache, $c->get(LoggerInterface::class));
}
return $cache;
});
Strategy por configuração
$app->bind(PaymentGateway::class, function () use ($app) {
return match ($app->getConfig()->get('payment.gateway')) {
'stripe' => new StripeGateway(),
'paypal' => new PayPalGateway(),
default => throw new RuntimeException('Gateway de pagamento inválido'),
};
});
Testando com o Contêiner
Substitua dependências por dublês com instance() antes de resolver o serviço:
use PHPUnit\Framework\TestCase;
use PivotPHP\Core\Core\Application;
use PivotPHP\Core\Providers\Container;
class UserServiceTest extends TestCase
{
public function testUsaRepositorioInjetado(): void
{
$app = new Application();
$repo = $this->createMock(UserRepositoryInterface::class);
$app->instance(UserRepositoryInterface::class, $repo);
$app->bind(UserService::class, fn(Container $c) => new UserService(
$c->get(UserRepositoryInterface::class)
));
$this->assertInstanceOf(UserService::class, $app->make(UserService::class));
}
}
Boas Práticas
- Registre por interface: vincule interfaces a fábricas que constroem a implementação.
- Sempre use closures para objetos: o contêiner não instancia classes a partir do nome.
- Prefira injeção no construtor: resolva dependências na fábrica e passe-as ao construtor, em vez de chamar o contêiner dentro das classes.
- Use
singletonpara serviços caros: conexões e clientes HTTP raramente precisam ser recriados. - Organize em provedores: agrupe registros relacionados em provedores de serviço.