O contêiner de serviços do Laravel oferece várias maneiras de registrar e gerenciar instâncias de classes. Vamos explorar os detalhes de cada método de vinculação e suas implementações internas.
Vinculação (Bind)
O método bind é a forma mais comum de registrar um serviço no contêiner. Ele suporta três tipos de vinculação:
- Vinculação de uma classe a si mesma.
- Vinculação de uma classe a uma Closure (função anônima).
- Vinculação de uma interface a uma implementação concreta.
Implementação do bind
Vamos analisar o código fonte do método bind:
/**
* Registra uma vinculação no contêiner.
*
* @param string|array $abstract O nome abstrato do serviço.
* @param \Closure|string|null $concrete A implementação concreta ou Closure.
* @param bool $shared Indica se a instância deve ser um singleton.
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// Remove instâncias obsoletas associadas a este binding.
$this->dropStaleInstances($abstract);
// Se nenhuma implementação concreta foi fornecida, usa o nome abstrato como concreto.
if (is_null($concrete)) {
$concrete = $abstract;
}
// Se a implementação não é uma Closure, cria uma Closure que a constrói.
// Isso permite a instanciação preguiçosa (lazy instantiation).
if (! $concrete instanceof Closure) {
$concrete = $this->createClosureForBinding($abstract, $concrete);
}
// Armazena a configuração da vinculação (Closure e se é compartilhada).
$this->bindings[$abstract] = compact('concrete', 'shared');
// Se o serviço abstrato já foi resolvido, dispara um evento de "rebound".
if ($this->isResolved($abstract)) {
$this->rebound($abstract);
}
}
Passos da Vinculação
- Limpeza de Instâncias Obsoletas: O método
dropStaleInstancesremove quaisquer instâncias singleton e aliases antigos associados à chave abstrata, preparando o contêiner para a nova vinculação. - Criação da Closure: Se a implementação fornecida não for uma Closure (por exemplo, um nome de classe), o método
createClosureForBinding(uma versão adaptada dogetClosureoriginal) cria uma Closure que encapsula a lógica de construção dessa classe. Isso garante a instanciação preguiçosa. - Registro da Vinculação: A configuração (a Closure de construção e o flag
shared) é armazenada no array$this->bindings, associada à chave abstrata. - Callback de Rebinding: Se o serviço abstrato já tiver sido resolvido anteriormente, o método
reboundé chamado. Isso permite que quaisquer objetos já instanciados sejam atualizados para refletir a nova vinculação, através de callbacks registrados.
Limpeza de Instâncias Obsoletas (dropStaleInstances)
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract]); // Remove a instância singleton existente.
unset($this->aliases[$abstract]); // Remove qualquer alias associado.
}
Criação da Closure (createClosureForBinding)
Esta função é crucial para a instanciação preguiçosa e para lidar com dependências recursivas. Ela envolve a construção da classe dentro de uma Closure.
protected function createClosureForBinding($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
// Se a classe abstrata é a mesma que a concreta (auto-vinculação), constrói diretamente.
if ($abstract === $concrete) {
return $container->build($concrete);
}
// Caso contrário, usa makeWith para resolver a dependência, permitindo recursão.
return $container->makeWith($concrete, $parameters);
};
}
A lógica aqui é que, para auto-vinculações (ex: bind(A::class, A::class)), a construção direta com build evita um loop infinito. Para outras vinculações, makeWith é usado para resolver a dependência, o que pode levar a um processo recursivo de resolução se a implementação também for uma interface ou classe vinculada.
Callback de Rebinding (rebound)
Este método é acionado quando uma vinculação é modificada e o serviço já foi previamente resolvido. Ele garante que quaisquer instâncias existentes sejam atualizadas.
protected function rebound($abstract)
{
// Resolve novamente a instância para obter a versão atualizada.
$instance = $this->make($abstract);
// Executa todos os callbacks de rebound registrados para esta abstração.
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
}
protected function getReboundCallbacks($abstract)
{
// Retorna os callbacks registrados para a abstração ou um array vazio.
return $this->reboundCallbacks[$abstract] ?? [];
}
Os callbacks de rebound são registrados usando o método rebinding:
public function rebinding($abstract, Closure $callback)
{
$abstract = $this->getAlias($abstract); // Resolve aliases para obter o nome final.
// Adiciona o callback à lista de callbacks de rebound.
$this->reboundCallbacks[$abstract][] = $callback;
// Se a abstração já estiver vinculada, resolve-a imediatamente para acionar o callback.
if ($this->bound($abstract)) {
$this->make($abstract);
}
}
Singleton (Singleton)
O método singleton é uma forma abreviada de bind onde o parâmetro shared é definido como true.
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true); // Chama bind com shared = true.
}
Instância (Instance)
O método instance registra uma instância concreta diretamente como um singleton, sem a necessidade de resolver ou construir a classe.
public function instance($abstract, $instance)
{
// Remove quaisquer bindings ou aliases antigos que possam conflitar.
$this->removeAbstractAlias($abstract);
unset($this->aliases[$abstract]);
// Armazena a instância diretamente.
$this->instances[$abstract] = $instance;
// Se a abstração já estava vinculada de alguma forma, dispara o callback de rebound.
if ($this->isBound($abstract)) {
$this->rebound($abstract);
}
}
protected function removeAbstractAlias($searched)
{
// Remove a entrada do alias das listas internas para evitar conflitos.
if (!isset($this->aliases[$searched])) {
return;
}
foreach ($this->abstractAliases as $abstract => $aliases) {
foreach ($aliases as $index => $alias) {
if ($alias == $searched) {
unset($this->abstractAliases[$abstract][$index]);
}
}
}
}
public function isBound($abstract)
{
// Verifica se a abstração está registrada como binding, instância ou alias.
return isset($this->bindings[$abstract]) ||
isset($this->instances[$abstract]) ||
$this->isAlias($abstract);
}
Vinculação Contextual (Contextual Binding)
As vinculações contextuais permitem especificar implementações específicas para dependências dentro de um construtor de classe particular. Isso é útil quando uma mesma interface precisa de diferentes implementações dependendo da classe que a requer.
O processo envolve o método when, que retorna um ContextualBindingBuilder.
// Exemplo de uso:
// $this->app->when(PhotoController::class)
// ->needs(Filesystem::class)
// ->give(function () {
// return Storage::disk('local');
// });
public function when($concrete)
{
// Retorna um builder para definir vinculações contextuais para a classe $concrete.
// $concrete é resolvido para seu alias final.
return new ContextualBindingBuilder($this, $this->getAlias($concrete));
}
// Classe ContextualBindingBuilder
class ContextualBindingBuilder
{
protected $container;
protected $concrete; // A classe que requer a dependência.
public function __construct(Container $container, $concrete)
{
$this->container = $container;
$this->concrete = $concrete;
}
public function needs($abstract)
{
$this->needs = $abstract; // A dependência abstrata a ser resolvida.
return $this;
}
public function give($implementation)
{
// Adiciona a vinculação contextual ao contêiner.
$this->container->addContextualBinding(
$this->concrete, $this->needs, $implementation
);
}
}
// Método no Container
public function addContextualBinding($concrete, $abstract, $implementation)
{
// Armazena a implementação contextual.
// A chave é a classe concreta, o valor é um array mapeando abstrações para implementações.
$this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
}
Quando o contêiner resolve uma dependência, ele primeiro verifica se existe uma vinculação contextual definida para a classe solicitante e a dependência específica.
Vinculação de Tag (Tag Binding)
As vinculações de tag permitem agrupar múltiplos serviços sob uma mesma tag. Isso é útil para recuperar todos os serviços associados a uma tag específica.
public function tag($abstracts, $tags)
{
// Garante que $tags seja um array.
$tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);
foreach ($tags as $tag) {
// Inicializa o array de tags se não existir.
if (!isset($this->tags[$tag])) {
$this->tags[$tag] = [];
}
// Adiciona cada abstração à lista de tags correspondentes.
foreach ((array) $abstracts as $abstract) {
$this->tags[$tag][] = $abstract;
}
}
}
Para resolver todos os serviços associados a uma tag, você usaria o método make com o nome da tag.
Vinculação via Array
O contêiner implementa a interface ArrayAccess, permitindo o uso de sintaxe de array para vinculações.
public function offsetSet($key, $value)
{
// Se o valor for uma Closure, vincula diretamente.
// Caso contrário, cria uma Closure que retorna o valor.
$this->bind($key, $value instanceof Closure ? $value : function () use ($value) {
return $value;
});
}
Extensão (Extend)
O método extend permite modifiacr ou estender uma instância já existente no contêiner.
public function extend($abstract, Closure $closure)
{
$abstract = $this->getAlias($abstract); // Resolve o alias final.
// Se a instância já existe (não é singleton registrado via bind apenas),
// modifica a instância existente e dispara o rebound.
if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $closure($this->instances[$abstract], $this);
$this->rebound($abstract);
} else {
// Se não é uma instância existente, armazena a closure de extensão.
// Ela será aplicada na próxima vez que a instância for criada.
$this->extenders[$abstract][] = $closure;
// Se o serviço já foi resolvido, dispara o rebound para aplicar a extensão imediatamente.
if ($this->resolved($abstract)) { // Nota: 'resolved' vs 'isResolved'
$this->rebound($abstract);
}
}
}
As extensões para serviços não instanciados (registrados via bind ou singleton) são armazenadas em $this->extenders e aplicadas quando o serviço é resolvido pela primeira vez.
Eventos do Contêiner
O contêiner dispara eventos durante o processo de resolução de serviços, usando os métodos resolving e afterResolving.
public function resolving($abstract, Closure $callback = null)
{
$abstract = $this->getAlias($abstract); // Resolve aliases.
// Se apenas um parâmetro é uma Closure, é um callback global.
if (is_null($callback) && $abstract instanceof Closure) {
$this->globalResolvingCallbacks[] = $abstract;
} else {
// Caso contrário, registra o callback para a abstração específica.
$this->resolvingCallbacks[$abstract][] = $callback;
}
}
public function afterResolving($abstract, Closure $callback = null)
{
$abstract = $this->getAlias($abstract); // Resolve aliases.
// Se apenas um parâmetro é uma Closure, é um callback global.
if ($abstract instanceof Closure && is_null($callback)) {
$this->globalAfterResolvingCallbacks[] = $abstract;
} else {
// Caso contrário, registra o callback para a abstração específica.
$this->afterResolvingCallbacks[$abstract][] = $callback;
}
}
Callbacks registrados com resolving são executados antes da instanciação do objeto, enquanto os callbacks de afterResolving são executados após a instanciação e injeção de dependências.