paint-brush
Laravel Under The Hood - Como estender a estruturaby@oussamamater
1,093
1,093

Laravel Under The Hood - Como estender a estrutura

Oussama Mater6m2024/05/29
Read on Terminal Reader

O Laravel encapsula o FakerPHP, que normalmente acessamos através do helper `fake()`. Faker PHP vem com modificadores como `valid()` e `unique()`, mas você pode usar apenas um de cada vez. Isso me fez pensar: e se quisermos criar nosso próprio modificador? Por exemplo, `uniqueAndValid()` ou qualquer outro modificador.
featured image - Laravel Under The Hood - Como estender a estrutura
Oussama Mater HackerNoon profile picture

Há alguns dias, eu estava corrigindo um teste instável e descobri que precisava de alguns valores exclusivos e válidos em minha fábrica. O Laravel envolve o FakerPHP, que normalmente acessamos através do auxiliar fake() . O FakerPHP vem com modificadores como valid() e unique() , mas você pode usar apenas um de cada vez, então você não pode fazer fake()->unique()->valid() , que é exatamente o que eu precisava.


Isso me fez pensar: e se quisermos criar nosso próprio modificador? Por exemplo, uniqueAndValid() ou qualquer outro modificador. Como podemos estender a estrutura?

Pensando alto

Estarei abandonando minha linha de pensamento.


Antes de saltar para qualquer solução excessivamente projetada, sempre quero verificar se existe uma opção mais simples e entender com o que estou lidando. Então, vamos dar uma olhada no auxiliar fake() :

 function fake($locale = null) { if (app()->bound('config')) { $locale ??= app('config')->get('app.faker_locale'); } $locale ??= 'en_US'; $abstract = \Faker\Generator::class.':'.$locale; if (! app()->bound($abstract)) { app()->singleton($abstract, fn () => \Faker\Factory::create($locale)); } return app()->make($abstract); }

Lendo o código, podemos ver que o Laravel vincula um singleton ao contêiner. Porém, se inspecionarmos o abstrato, é uma classe regular que não implementa nenhuma interface, e o objeto é criado por meio de uma fábrica. Isso complica as coisas. Por que?


  1. Porque se fosse uma interface, poderíamos simplesmente criar uma nova classe que estendesse a classe \Faker\Generator base, adicionar alguns novos recursos e revinculá-la ao contêiner. Mas não temos esse luxo.


  2. Há uma fábrica envolvida. Isso significa que não é uma simples instanciação; há alguma lógica sendo executada. Neste caso, a fábrica adiciona alguns provedores (PhoneNumber, Text, UserAgent, etc.). Portanto, mesmo que tentemos religar, teremos que usar a fábrica, que retornará o \Faker\Generator original.


Soluções 🤔? Alguém poderia pensar: “O que nos impede de criar nossa própria fábrica que devolva o novo gerador conforme descrito no ponto 1?” Bem, nada, podemos fazer isso, mas não faremos! Usamos uma estrutura por vários motivos, um deles sendo atualizações. O que acontecerá se o FakerPHP adicionar um novo provedor ou fizer uma grande atualização? O Laravel ajustará o código e as pessoas que não fizeram nenhuma alteração não notarão nada. No entanto, ficaríamos de fora e nosso código poderia até quebrar (provavelmente). Então, sim, não queremos ir tão longe.

Então, o que fazemos?

Agora que exploramos as opções básicas, podemos começar a pensar nas mais avançadas, como padrões de design. Não precisamos de uma implementação exata, apenas de algo familiar ao nosso problema. É por isso que sempre digo que é bom conhecê-los. Neste caso, podemos “decorar” a classe Generator adicionando novos recursos enquanto mantemos os antigos. Parece bom? Vamos ver como!


Primeiro, vamos criar uma nova classe, FakerGenerator :

 <?php namespace App\Support; use Closure; use Faker\Generator; use Illuminate\Support\Traits\ForwardsCalls; class FakerGenerator { use ForwardsCalls; public function __construct(private readonly Generator $generator) { } public function uniqueAndValid(Closure $validator = null): UniqueAndValidGenerator { return new UniqueAndValidGenerator($this->generator, $validator); } public function __call($method, $parameters): mixed { return $this->forwardCallTo($this->generator, $method, $parameters); } }

Este será o nosso “decorador” (mais ou menos). É uma classe simples que espera o Generator base como uma dependência e introduz um novo modificador, uniqueAndValid() . Ele também usa o atributo ForwardsCalls do Laravel, que permite fazer proxy de chamadas para o objeto base.


Essa característica possui dois métodos: forwardCallTo e forwardDecoratedCallTo . Use o último quando quiser encadear métodos no objeto decorado. No nosso caso, teremos sempre uma única chamada.


Também precisamos implementar UniqueAndValidGenerator , que é o modificador personalizado, mas esse não é o objetivo do artigo. Se você estiver interessado na implementação, esta classe é basicamente uma mistura do ValidGenerator e UniqueGenerator que vem com o FakerPHP, você pode encontrá-la aqui .


Agora, vamos estender o framework, no AppServiceProvider :

 <?php namespace App\Providers; use Closure; use Faker\Generator; use App\Support\FakerGenerator; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->extend( $this->fakerAbstractName(), fn (Generator $base) => new FakerGenerator($base) ); } private function fakerAbstractName(): string { // This is important, it matches the name bound by the fake() helper return Generator::class . ':' . app('config')->get('app.faker_locale'); } }


O método extend() verifica se um resumo correspondente ao nome fornecido foi vinculado ao contêiner. Se sim, ele substitui seu valor pelo resultado do fechamento, veja:

 // Laravel: src/Illuminate/Container/Container.php public function extend($abstract, Closure $closure) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) { // You are interested here $this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract); } else { $this->extenders[$abstract][] = $closure; if ($this->resolved($abstract)) { $this->rebound($abstract); } } }

É por isso que definimos o método fakerAbstractName() , que gera o mesmo nome que o auxiliar fake() se liga no contêiner.


Verifique novamente o código acima se você perdeu, deixei um comentário.


Agora, toda vez que chamarmos fake() , uma instância de FakerGenerator será retornada e teremos acesso ao modificador personalizado que introduzimos. Cada vez que invocamos uma chamada que não existe na classe FakerGenerator , __call() será acionado e fará proxy para o Generator base usando o método forwardCallTo() .


É isso! Finalmente posso fazer fake()->uniqueAndValid()->randomElement() , e funciona perfeitamente!


Antes de concluirmos, quero ressaltar que este não é um padrão puramente decorador. Contudo, os padrões não são textos sagrados; ajuste-os para atender às suas necessidades e resolver o problema.


Conclusão

Frameworks são incrivelmente úteis e o Laravel vem com muitos recursos integrados. No entanto, eles não podem cobrir todos os casos extremos em seus projetos e, às vezes, você pode chegar a um beco sem saída. Quando isso acontecer, você sempre poderá estender a estrutura. Vimos como é simples e espero que você tenha entendido a ideia principal, que se aplica além deste exemplo do Faker.


Comece sempre de forma simples e procure a solução mais simples para o problema. A complexidade virá quando for necessária, portanto, se a herança básica funcionar, não há necessidade de implementar um decorador ou qualquer outra coisa. Ao ampliar a estrutura, certifique-se de não ir longe demais, onde a perda supera o ganho. Você não quer acabar mantendo uma parte da estrutura sozinho.