Você acabou de instalar um novo aplicativo Laravel, inicializá-lo e obter a página de boas-vindas. Como todo mundo, você tenta ver como ele é renderizado, então entra no arquivo e encontra este código: web.php <?php use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); É óbvio como obtivemos a visão de boas-vindas, mas você está curioso para saber como funciona o roteador do Laravel, então decide mergulhar no código. A suposição inicial é: há uma classe na qual estamos chamando um método estático . No entanto, ao clicar nele, não existe o método ali. Então, que tipo de magia negra está acontecendo? Vamos desmistificar isso! Route get() get() Fachadas Regulares Observe que removi a maior parte dos PHPDocs e incorporei os tipos apenas para simplificar, "..." refere-se a mais código. Eu sugiro fortemente abrir seu IDE e acompanhar o código para evitar qualquer confusão. Seguindo nosso exemplo, vamos explorar a classe . Route <?php namespace Illuminate\Support\Facades; class Route extends Facade { // ... protected static function getFacadeAccessor(): string { return 'router'; } } Não há muito aqui, apenas o método que retorna a string . Tendo isso em mente, vamos passar para a classe pai. getFacadeAccessor() router <?php namespace Illuminate\Support\Facades; use RuntimeException; // ... abstract class Facade { // ... public static function __callStatic(string $method, array $args): mixed { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); } } Dentro da classe pai, existem muitos métodos, mas não existe um método . Mas há um interessante, o método . É um método , invocado sempre que um método estático indefinido, como no nosso caso, é chamado. Portanto, nossa chamada representa o que passamos ao invocar , a rota e um que retorna a visualização de boas-vindas. get() __callStatic() mágico get() __callStatic('get', ['/', Closure()]) Route::get() / Closure() Quando é acionado, ele primeiro tenta definir uma variável chamando , o contém a classe real para a qual a chamada deve ser encaminhada, vamos dar uma olhada mais de perto, isso fará sentido daqui a pouco __callStatic() $instance getFacadeRoot() $instance // Facade.php public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } Ei, veja, é o da classe filha , que sabemos que retornou a string . Essa string é então passada para , que tenta resolvê-la para uma classe, uma espécie de mapeamento que diz "Que classe esta string representa?" Vamos ver. getFacadeAccessor() Route router router resolveFacadeInstance() // Facade.php protected static function resolveFacadeInstance($name) { if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { if (static::$cached) { return static::$resolvedInstance[$name] = static::$app[$name]; } return static::$app[$name]; } } Ele primeiro verifica se um array estático, , tem um valor definido com o fornecido (que, novamente, é ). Se encontrar uma correspondência, apenas retornará esse valor. Este é o cache do Laravel para otimizar um pouco o desempenho. Esse armazenamento em cache ocorre em uma única solicitação. Se esse método for chamado várias vezes com o mesmo argumento na mesma solicitação, ele usará o valor armazenado em cache. Vamos supor que seja a chamada inicial e prosseguir. $resolvedInstance $name router Em seguida, ele verifica se está definido e é uma instância do contêiner do aplicativo $app $app // Facade.php protected static \Illuminate\Contracts\Foundation\Application $app; Se você está curioso para saber o que é um contêiner de aplicação, pense nele como uma caixa onde suas classes são armazenadas. Quando você precisar dessas aulas, basta acessar essa caixa. Às vezes, esse contêiner faz um pouco de mágica. Mesmo que a caixa esteja vazia e você procure uma aula, ela a receberá para você. Isso é assunto para outro artigo. Agora, você pode se perguntar: "Quando é definido?", porque precisa ser, caso contrário, não teremos nosso . Este contêiner de aplicativo é definido durante o processo de inicialização do nosso aplicativo. Vamos dar uma olhada rápida na classe : $app $instance \Illuminate\Foundation\Http\Kernel <?php namespace Illuminate\Foundation\Http; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Facade; use Illuminate\Contracts\Http\Kernel as KernelContract; // ... class Kernel implements KernelContract { // ... protected $app; protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, // <- this guy \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; public function bootstrap(): void { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } } } Quando uma solicitação chega, ela é enviada ao roteador. Pouco antes disso, o método é invocado, que usa o array para preparar a aplicação. Se você explorar o método na classe , ele itera por meio desses bootstrappers, chamando seu método . bootstrap() bootstrappers bootstrapWith() \Illuminate\Foundation\Application bootstrap() Para simplificar, vamos nos concentrar apenas em , que sabemos que contém um método que será invocado em \Illuminate\Foundation\Bootstrap\RegisterFacades bootstrap() bootstrapWith() <?php namespace Illuminate\Foundation\Bootstrap; use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\AliasLoader; use Illuminate\Foundation\PackageManifest; use Illuminate\Support\Facades\Facade; class RegisterFacades { // ... public function bootstrap(Application $app): void { Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); // Interested here AliasLoader::getInstance(array_merge( $app->make('config')->get('app.aliases', []), $app->make(PackageManifest::class)->aliases() ))->register(); } } E aí está, estamos configurando o contêiner do aplicativo na classe usando o método estático Facade setFacadeApplication(). // RegisterFacades.php public static function setFacadeApplication($app) { static::$app = $app; } Veja, atribuímos a propriedade que estamos testando em . Isso responde à pergunta; vamos continuar. $app resolveFacadeInstance() // Facade.php protected static function resolveFacadeInstance($name) { if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { if (static::$cached) { return static::$resolvedInstance[$name] = static::$app[$name]; } return static::$app[$name]; } } Confirmamos que está definido durante a inicialização do aplicativo. A próxima etapa é verificar se a instância resolvida deve ser armazenada em cache, verificando , cujo padrão é verdadeiro. Por fim, recuperamos a instância do contêiner do aplicativo; em nosso caso, é como pedir para fornecer qualquer classe vinculada à string . $app $cached static::$app['router'] router Agora, você deve estar se perguntando por que acessamos como um array, apesar de ser uma instância do contêiner do aplicativo, portanto, um . Bem, você está certo! No entanto, o contêiner do aplicativo implementa uma interface PHP chamada , permitindo acesso semelhante a um array. Podemos dar uma olhada para confirmar este fato: $app object ArrayAccess <?php namespace Illuminate\Container; use ArrayAccess; // <- this guy use Illuminate\Contracts\Container\Container as ContainerContract; class Container implements ArrayAccess, ContainerContract { // ... } Portanto, de fato retorna uma instância vinculada à string , especificamente . Como eu sabia? Dê uma olhada na fachada ; frequentemente, você encontrará um PHPDoc sugerindo o que essa fachada esconde ou, mais precisamente, para qual classe nossas chamadas de método serão proxy. resolveFacadeInstance() router \Illuminate\Routing\Router Route @see Agora, de volta ao nosso método . __callStatic <?php namespace Illuminate\Support\Facades; use RuntimeException; // ... abstract class Facade { // ... public static function __callStatic(string $method, array $args): mixed { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); } } Temos , um objeto da classe . Testamos se está definido (o que, no nosso caso, está confirmado) e invocamos diretamente o método nele. Então, terminamos com. $instance \Illuminate\Routing\Router // Facade.php return $instance->get('/', Closure()); E agora, você pode confirmar que existe na classe . get() \Illuminate\Routing\Router <?php namespace Illuminate\Routing; use Illuminate\Routing\Route; use Illuminate\Contracts\Routing\BindingRegistrar; use Illuminate\Contracts\Routing\Registrar as RegistrarContract; // ... class Router implements BindingRegistrar, RegistrarContract { // ... public function get(string $uri, array|string|callable|null $action = null): Route { return $this->addRoute(['GET', 'HEAD'], $uri, $action); } } Isso encerra tudo! Não foi tão difícil no final? Para recapitular, uma fachada retorna uma string vinculada ao contêiner. Por exemplo, pode estar vinculado à classe . Quando invocamos estaticamente um método indefinido em uma fachada, , por exemplo, intervém. hello-world HelloWorld HelloWorldFacade __callStatic() Ele resolve a string registrada em seu método para o que quer que esteja vinculado ao contêiner e faz proxy de nossa chamada para essa instância recuperada. Assim, terminamos com . Essa é a essência disso! Ainda não clicou para você? Vamos criar nossa fachada então! getFacadeAccessor() (new HelloWorld())->method() Vamos fazer nossa fachada Digamos que temos esta classe: <?php namespace App\Http\Controllers; class HelloWorld { public function greet(): string { return "Hello, World!"; } } O objetivo é invocar . Para fazer isso, vincularemos nossa classe ao contêiner do aplicativo. Primeiro, navegue até . HelloWorld::greet() AppServiceProvider <?php namespace App\Providers; use App\Http\Controllers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->bind('hello-world', function ($app) { return new HelloWorld; }); } // ... } Agora, sempre que solicitamos do contêiner do nosso aplicativo (ou da caixa, como mencionei anteriormente), ele retorna uma instância de . O que sobrou? Basta criar uma fachada que retorne a string . hello-world HelloWorld hello-world <?php namespace App\Http\Facades; use Illuminate\Support\Facades\Facade; class HelloWorldFacade extends Facade { protected static function getFacadeAccessor() { return 'hello-world'; } } Com isso implementado, estamos prontos para usá-lo. Vamos chamá-lo dentro do nosso web.php. <?php use App\Http\Facades; use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorldFacade::greet(); // Hello, World! }); Sabemos que não existe na fachada , é acionado. Ele extrai uma classe representada por uma string ( no nosso caso) do contêiner do aplicativo. E já fizemos essa vinculação no ; nós o instruímos a fornecer uma instância de sempre que alguém solicitar um . Conseqüentemente, qualquer chamada, como , operará na instância recuperada de . E é isso. greet() HelloWorldFacade __callStatic() hello-world AppServiceProvider HelloWorld hello-world greet() HelloWorld Parabéns! Você criou sua própria fachada! Fachadas em tempo real do Laravel Agora que você conhece bem as fachadas, há mais um truque de mágica para desvendar. Imagine poder chamar sem criar uma fachada, usando . HelloWorld::greet() fachadas em tempo real Vamos dar uma olhada: <?php use Facades\App\Http\Controllers; // Notice the prefix use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorld::greet(); // Hello, World! }); Ao prefixar o namespace do controlador com , obtemos o mesmo resultado anterior. Mas é certo que o controlador não possui nenhum método estático chamado ! E de onde vem ? Eu entendo que isso pode parecer algum tipo de feitiçaria, mas uma vez que você entende, é bem simples. Facades HelloWorld greet() Facades\App\Http\Controllers\HelloWorld Vamos dar uma olhada mais de perto em que verificamos anteriormente, a classe responsável por configurar o \Illuminate\Foundation\Bootstrap\RegisterFacades $app: <?php namespace Illuminate\Foundation\Bootstrap; use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\AliasLoader; use Illuminate\Foundation\PackageManifest; use Illuminate\Support\Facades\Facade; class RegisterFacades { public function bootstrap(Application $app): void { Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); AliasLoader::getInstance(array_merge( $app->make('config')->get('app.aliases', []), $app->make(PackageManifest::class)->aliases() ))->register(); // Interested here } } Você pode ver no final que o método é invocado. Vamos dar uma olhada por dentro: register() <?php namespace Illuminate\Foundation; class AliasLoader { // ... protected $registered = false; public function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } } A variável é inicialmente definida como . Portanto, inserimos a instrução e chamamos o método . Agora, vamos explorar sua implementação. $registered false if prependToLoaderStack() // AliasLoader.php protected function prependToLoaderStack(): void { spl_autoload_register([$this, 'load'], true, true); } É aqui que a mágica acontece! Laravel está chamando a função , uma função PHP integrada que é acionada ao tentar acessar uma classe indefinida. Ele define a lógica a ser executada em tais situações. Neste caso, o Laravel escolhe invocar o método ao encontrar uma chamada indefinida. spl_autoload_register() load() Além disso, passa automaticamente o nome da classe indefinida para qualquer método ou função que ela chamar. spl_autoload_register() Vamos explorar o método ; deve ser a chave. load() // AliasLoader.php public function load($alias) { if (static::$facadeNamespace && str_starts_with($alias, static::$facadeNamespace)) { $this->loadFacade($alias); return true; } if (isset($this->aliases[$alias])) { return class_alias($this->aliases[$alias], $alias); } } Verificamos se está definido e se qualquer classe passada, no nosso caso, começa com o que está definido em $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace A lógica verifica se está definido e se a classe passada, em nosso caso (que é indefinida), começa com o valor especificado em $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace. // AliasLoader.php protected static $facadeNamespace = 'Facades\\'; Como prefixamos o namespace do nosso controlador com , satisfazendo a condição, prosseguimos para Facades loadFacade() // AliasLoader.php protected function loadFacade($alias) { require $this->ensureFacadeExists($alias); } Aqui, o método requer qualquer caminho retornado de . Portanto, o próximo passo é aprofundar sua implementação. ensureFacadeExists() // AliasLoader.php protected function ensureFacadeExists($alias) { if (is_file($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) { return $path; } file_put_contents($path, $this->formatFacadeStub( $alias, file_get_contents(__DIR__.'/stubs/facade.stub') )); return $path; } Primeiro, é feita uma verificação para verificar se um arquivo chamado existe. No nosso caso, este arquivo não está presente, acionando a próxima etapa: . Esta função cria um arquivo e o salva no especificado. O conteúdo do arquivo é gerado por , que, a julgar pelo nome, cria uma fachada a partir de um stub. Se você inspecionasse , encontraria o seguinte: framework/cache/facade-'.sha1($alias).'.php' file_put_contents() $path formatFacadeStub() facade.stub <?php namespace DummyNamespace; use Illuminate\Support\Facades\Facade; /** * @see \DummyTarget */ class DummyClass extends Facade { /** * Get the registered name of the component. */ protected static function getFacadeAccessor(): string { return 'DummyTarget'; } } Parece familiar? Isso é essencialmente o que fizemos manualmente. Agora, substitui o conteúdo fictício por nossa classe indefinida após remover o prefixo . Esta fachada atualizada é então armazenada. Conseqüentemente, quando requer o arquivo, ele o faz corretamente e acaba exigindo o seguinte arquivo: formatFacadeStub() Facades\\ loadFacade() <?php namespace Facades\App\Http\Controllers; use Illuminate\Support\Facades\Facade; /** * @see \App\Http\Controllers\HelloWorld */ class HelloWorld extends Facade { /** * Get the registered name of the component. */ protected static function getFacadeAccessor(): string { return 'App\Http\Controllers\HelloWorld'; } } E agora, no fluxo normal, pedimos ao contêiner do aplicativo que retorne qualquer instância vinculada à string . Você deve estar se perguntando: não vinculamos essa string a nada, nem tocamos em nosso . Mas lembra do que mencionei sobre o contêiner do aplicativo logo no início? App\Http\Controllers\HelloWorld AppServiceProvider , mas com uma condição, a classe não deve ter construtor. Caso contrário, não saberia como construí-lo para você. No nosso caso, nossa classe não precisa de nenhum argumento para ser construída. Assim, o contêiner resolve, retorna e todas as chamadas são enviadas por proxy para ele. Mesmo que a caixa esteja vazia, ela retornará a instância HelloWorld Recapitulando fachadas em tempo real: prefixamos nossa classe com . Durante a inicialização da aplicação, o Laravel registra , que é acionado quando chamamos classes indefinidas. Eventualmente leva ao método . Dentro de , verificamos se a classe indefinida atual tem o prefixo . Ele corresponde, então o Laravel tenta carregá-lo. Facades spl_autoload_register() load() load() Facades Como a fachada não existe, ela é criada a partir de um stub e então requer o arquivo. E pronto! Você tem uma fachada normal, mas esta foi criada na hora. Muito legal, hein? Conclusão Parabéns por chegar até aqui! Eu entendo que pode ser um pouco opressor. Sinta-se à vontade para voltar e reler quaisquer seções que não tenham agradado a você. Acompanhar seu IDE também pode ajudar. Mas ei, chega de magia negra, deve ser bom, pelo menos foi assim que me senti da primeira vez! E lembre-se, da próxima vez que você chamar um método estaticamente, pode não ser o caso 🪄