Skip to main content

O Accelero trabalha FORTE com injeção de dependência.

Usamos um container central ( SinFramework\DependencyContainer) que implementa MAIS-OU-MENOS um PSR-11 (ele não estoura NotFoundExceptionInterface).

Esse container é simples de usar e tem auto-wiring, o que facilita bastante a vida do programador.

REGISTRO DE DEPENDÊNCIAS

Durante o bootstrap do sistema são registradas as dependências necessárias. Todos os arquivos existentes em private/config/injections são registrados, com a proteção de "subdiretórios-são-carregados-apenas-se-houver-um-módulo-ativo-com-o-mesmo-nome".

Se a mesma dependência for registrada duas vezes, vale a última.

Todos os arquivos devem retornar um array key/value, onde a chave é o "nome da coisa registrada" e o valor é "a implementação".

É possível (e MUITO recomendado) fazer lazy-load das dependências retornando Closures durante o registro (exemplos mais abaixo).

É possível (mas tome cuidado) registrar singletons. Detalhes mais abaixo.

ESTRUTURA BÁSICA DO REGISTRO

Alguns exemplos de registro de dependências:

//private/config/injections/my_injections.php
return [
MyInterface::class => fn() : MyInterface => new MyConcreteImplementation(),
'magicNumber' => 7,
];

BUSCANDO UMA DEPENDÊNCIA

Se for necessário buscar uma dependência qualquer no meio do código (basicamente um Service Locator), basta fazer assim:

$injection = Accelero::getInjection(MyInterface::class); //MyConcreteImplementation

Se o que se está pedindo não estiver registrado no container, o retorno é sempre NULL:

$injection = Accelero::getInjection('coisaNaoRegistrada'); //null

AUTO-WIRING

O container é especialmente útil pra construir automaticamente objetos complexos com injeção de dependência no construtor. Exemplo:

//private/config/injections/my_injections.php
return [
LoggerInterface::class => fn() => new Logger(.......),
];

//MyClass.php
class MyClass {
public function __construct(private readonly LoggerInterface $logger) {}
}

//teste.php
$injection = Accelero::getInjection(MyClass::class); //funciona mesmo sem jamais termos registrado MyClass

Quando se "pede" um objeto MyClass pro container, ele verifica automaticamente que o construtor exige um LoggerInterface. Daí o container busca um LoggerInterface no registro... e se o construtor do LoggerInterface pedisse alguma coisa, ele ia atrás dessa "alguma coisa", recursivamente, até conseguir resolver todas as dependências.

Se alguma coisa não for resolvível automaticamente, retorna-se NULL.

REGISTRO RECURSIVO DE DEPENDÊNCIAS

Em geral, é boa prática nunca usar new nos registros de dependência de interfaces, mas sempre "pedir a implementação que se deseja pro container". Isso facilita futuras refatorações e evita que você tenha que se preocupar com a construção do objeto na maior parte dos casos. Exemplo:

//private/config/injections/my_injections.php
return [
MyInterface::class => fn() : MyInterface => new MyConcreteImplementation(), //RUIM
MyInterface::class => fn() : MyInterface => Accelero::getInjection(MyConcreteImplementation::class), //BOM
];

LAZY-LOADING

Quando o retorno de algo registrado no container é uma Closure, essa Closure é executada automaticamente quando se pede aquilo pro container (e somente quando aquilo é solicitado). Exemplo:

//private/config/injections/my_injections.php
class Teste {
public static function print() : void {
echo 'Hello';
}
}

return [
'mykey' => Teste::print(), //VAI EXECUTAR O MÉTODO IMEDIATAMENTE NA HORA DO REGISTRO, MESMO SEM NINGUÉM "PEDIR"
'mykey2' => fn() => Teste::print(), //SÓ VAI EXECUTAR QUANDO ALGUÉM "PEDIR" ESSE mykey2
];

Em geral, é melhor SEMPRE retornar Closures ao registrar dependências, pois isso garante que tudo será lazy-loaded (menor uso de memória, mais rápido).

CACHE

Você pode configurar certas classes para usarem "cache no injetor de dependência". Às vezes "resolver a dependência" é caro, e é uma boa ideia guardar o resultado em cache por um tempo pra evitar ter que resolver novamente na sequência. Pra isso, basta incluir na definição da própria classe um attribute passando o TTL desejado em segundos:

#[Cacheable(ttl: 600)]
class MyClass {}

Como essa definição acontece na própria classe, NÃO É POSSÍVEL usar esse recurso para classes externas (pois não devemos mexer no source alheio). Nada impede de implementar algo manualmente durante o registro de dependência, no entanto:

//private/config/injections/my_injections.php
return [
MyClass::class => function() : MyClass {
$cache = Accelero::getInjection(CacheInterface::class);
$object = $cache->get(MyClass::class);
if (!$object) {
$object = new MyClass();
$cache->set(MyClass::class, $object, ttl: 600);
}
return $object;
}
];

SINGLETONS

Se você não fizer nada especial, todas as vezes que pedir um objeto para o container receberá uma nova instância:

class MyClass {}

var_dump(Accelero::getInjection(MyClass::class) === Accelero::getInjection(MyClass::class)); //false

Mas você pode dizer pro container que certas coisas devem ser registradas como singleton (uma única instância compartilhada). Existem duas formas de fazer isso.

Direto na definição da classe

#[Singleton]
class MyClass {}

var_dump(Accelero::getInjection(MyClass::class) === Accelero::getInjection(MyClass::class)); //true

Lista de singletons no próprio injetor de dependência

A classe Singleton injetada no container é um array de classes que devem ser tratadas como singleton:

//private/config/injections/singletons.php
return [
Singleton::class => [
MyClass::class,
CacheInterface::class,
...
]
];

var_dump(Accelero::getInjection(MyClass::class) === Accelero::getInjection(MyClass::class)); //true

DEV

É muito útil fazer um bypass de certas dependências durante o desenvolvimento. Cache, banco de dados, etc...

Como existe a regra "dependências registradas com o mesmo nome, vale a última", é possível registrar coisas "por cima do padrão" sem ter que mexer nos arquivos "originais".

As dependências são registradas na seguinte ordem (e a última sempre é "que vale"):

private/config/injections
plugins/**/injections
client/config/injections
dev/injections

A lógica/justificativa é a seguinte:

  • primeiro se carregam as injeções padrão
  • depois se carregam as injeções padrão DOS PLUGINS HABILITADOS
  • depois se carregam as injeções "do cliente" (customizações)
  • depois se carregam as injeções "do desenvolvedor" (só deve existir em ambiente dev ou em emergências)