Vous venez d'installer une nouvelle application Laravel, de la démarrer et d'obtenir la page d'accueil. Comme tout le monde, vous essayez de voir comment il est rendu, alors vous sautez dans le fichier et rencontrez ce code : web.php <?php use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); Il est évident que nous avons obtenu la vue de bienvenue, mais vous êtes curieux de savoir comment fonctionne le routeur de Laravel, alors vous décidez de vous plonger dans le code. L'hypothèse initiale est la suivante : il existe une classe sur laquelle nous appelons une méthode statique . Cependant, en cliquant dessus, il n’y a pas de méthode . Alors, quel genre de magie noire se produit ? Démystifions cela ! Route get() get() Façades régulières Veuillez noter que j'ai supprimé la plupart des PHPDocs et intégré les types juste pour plus de simplicité, "..." fait référence à plus de code. Je suggère fortement d'ouvrir votre IDE et de suivre le code pour éviter toute confusion. En suivant notre exemple, explorons la classe . Route <?php namespace Illuminate\Support\Facades; class Route extends Facade { // ... protected static function getFacadeAccessor(): string { return 'router'; } } Il n'y a pas grand chose ici, juste la méthode qui renvoie la chaîne . En gardant cela à l’esprit, passons à la classe parent. 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); } } Dans la classe parent, il existe de nombreuses méthodes, mais il n’existe pas de méthode . Mais il en existe une intéressante, la méthode . C'est une méthode , invoquée chaque fois qu'une méthode statique non définie, comme dans notre cas, est appelée. Par conséquent, notre appel représente ce que nous avons passé lors de l'appel , la route et un qui renvoie la vue de bienvenue. get() __callStatic() magique get() __callStatic('get', ['/', Closure()]) Route::get() / Closure() Lorsque est déclenché, il tente d'abord de définir une variable en appelant , la contient la classe réelle à laquelle l'appel doit être transféré, regardons de plus près, cela aura du sens dans un instant __callStatic() $instance getFacadeRoot() $instance // Facade.php public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } Hé, regarde, c'est le de la classe enfant , qui, nous le savons, a renvoyé la chaîne . Cette chaîne est ensuite transmise à , qui tente de la résoudre en une classe, une sorte de mappage indiquant « Quelle classe cette chaîne représente-t-elle ? » Voyons. 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]; } } Il vérifie d'abord si un tableau statique, , a une valeur définie avec le donné (qui, encore une fois, est ). S'il trouve une correspondance, il renvoie simplement cette valeur. Il s'agit de la mise en cache Laravel pour optimiser un peu les performances. Cette mise en cache se produit au sein d’une seule requête. Si cette méthode est appelée plusieurs fois avec le même argument dans la même requête, elle utilise la valeur mise en cache. Supposons qu'il s'agisse de l'appel initial et procédons. $resolvedInstance $name router Il vérifie ensuite si est défini et est une instance du conteneur d'application $app $app // Facade.php protected static \Illuminate\Contracts\Foundation\Application $app; Si vous êtes curieux de savoir ce qu'est un conteneur d'application, considérez-le comme une boîte dans laquelle vos cours sont stockés. Lorsque vous avez besoin de ces cours, il vous suffit d'accéder à cette case. Parfois, ce conteneur fait un peu de magie. Même si la boîte est vide et que vous souhaitez prendre un cours, elle l'obtiendra pour vous. C'est un sujet pour un autre article. Maintenant, vous vous demandez peut-être : "Quand est-il défini ?", car cela doit être le cas, sinon nous n'aurons pas notre . Ce conteneur d'application est défini lors du processus de démarrage de notre application. Jetons un coup d'œil rapide à la 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()); } } } Lorsqu'une requête arrive, elle est envoyée au routeur. Juste avant cela, la méthode est invoquée, qui utilise le tableau pour préparer l'application. Si vous explorez la méthode dans la classe , elle parcourt ces bootstrappers, appelant leur méthode . bootstrap() bootstrappers bootstrapWith() \Illuminate\Foundation\Application bootstrap() Pour plus de simplicité, concentrons-nous simplement sur , dont nous savons qu'il contient une méthode qui sera invoquée dans \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(); } } Et voilà, nous définissons le conteneur d'application sur la classe en utilisant la méthode statique Facade setFacadeApplication(). // RegisterFacades.php public static function setFacadeApplication($app) { static::$app = $app; } Vous voyez, nous attribuons la propriété que nous testons dans . Cela répond à la question ; nous allons continuer. $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]; } } Nous avons confirmé que est défini lors du démarrage de l'application. L'étape suivante consiste à vérifier si l'instance résolue doit être mise en cache en vérifiant , qui est par défaut true. Enfin, nous récupérons l'instance du conteneur d'application, dans notre cas, c'est comme demander de fournir n'importe quelle classe liée au string . $app $cached static::$app['router'] router Maintenant, vous vous demandez peut-être pourquoi nous accédons comme un tableau alors qu'il s'agit d'une instance du conteneur d'application, donc d'un . Eh bien, vous avez raison ! Cependant, le conteneur d'application implémente une interface PHP appelée , permettant un accès de type tableau. Nous pouvons y jeter un œil pour confirmer ce fait : $app object ArrayAccess <?php namespace Illuminate\Container; use ArrayAccess; // <- this guy use Illuminate\Contracts\Container\Container as ContainerContract; class Container implements ArrayAccess, ContainerContract { // ... } Ainsi, le renvoie en effet une instance liée à la chaîne , en particulier . Comment le savais-je ? Jetez un œil à la façade ; souvent, vous trouverez un PHPDoc faisant allusion à ce que cache cette façade ou, plus précisément, à quelle classe nos appels de méthode seront proxy. resolveFacadeInstance() router \Illuminate\Routing\Router Route @see Revenons maintenant à notre méthode . __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); } } Nous avons , un objet de la classe . Nous testons s'il est défini (ce qui, dans notre cas, est confirmé), et nous invoquons directement la méthode dessus. Donc, on se retrouve avec. $instance \Illuminate\Routing\Router // Facade.php return $instance->get('/', Closure()); Et maintenant, vous pouvez confirmer que existe dans la 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); } } C'est tout ! N'était-ce pas difficile au final ? Pour récapituler, une façade renvoie une chaîne liée au conteneur. Par exemple, pourrait être lié à la classe . Lorsque nous invoquons statiquement une méthode non définie sur une façade, par exemple, intervient. hello-world HelloWorld HelloWorldFacade __callStatic() Il résout la chaîne enregistrée dans sa méthode en tout ce qui est lié au conteneur et transmet notre appel à cette instance récupérée. Ainsi, nous nous retrouvons avec . C'est l'essentiel ! Vous n'avez toujours pas cliqué pour vous ? Créons alors notre façade ! getFacadeAccessor() (new HelloWorld())->method() Faisons notre façade Disons que nous avons cette classe : <?php namespace App\Http\Controllers; class HelloWorld { public function greet(): string { return "Hello, World!"; } } Le but est d'invoquer . Pour ce faire, nous allons lier notre classe au conteneur d'application. Tout d’abord, accédez à . 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; }); } // ... } Désormais, chaque fois que nous demandons à notre conteneur d'application (ou à la boîte, comme je l'ai mentionné plus tôt), il renvoie une instance de . Ce qui reste? Créez simplement une façade qui renvoie la chaîne . 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'; } } Une fois cela en place, nous sommes prêts à l'utiliser. Appelons-le dans notre web.php. <?php use App\Http\Facades; use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorldFacade::greet(); // Hello, World! }); Nous savons que n'existe pas sur la façade , est déclenché. Il extrait une classe représentée par une chaîne ( dans notre cas) du conteneur d'application. Et nous avons déjà effectué cette liaison dans ; nous lui avons demandé de fournir une instance de chaque fois que quelqu'un demande un . Par conséquent, tout appel, tel que , fonctionnera sur cette instance récupérée de . Et c'est tout. greet() HelloWorldFacade __callStatic() hello-world AppServiceProvider HelloWorld hello-world greet() HelloWorld Toutes nos félicitations! Vous avez créé votre propre façade ! Façades Laravel en temps réel Maintenant que vous comprenez bien les façades, il vous reste encore un tour de magie à dévoiler. Imaginez pouvoir appeler sans créer de façade, en utilisant . HelloWorld::greet() des façades en temps réel Regardons: <?php use Facades\App\Http\Controllers; // Notice the prefix use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorld::greet(); // Hello, World! }); En préfixant l'espace de noms du contrôleur avec , nous obtenons le même résultat que précédemment. Mais il est certain que le contrôleur ne possède pas de méthode statique nommée ! Et d'où vient ? Je comprends que cela puisse ressembler à de la sorcellerie, mais une fois que vous l'avez compris, c'est assez simple. Facades HelloWorld greet() Facades\App\Http\Controllers\HelloWorld Regardons de plus près que nous avons vérifié plus tôt, la classe responsable de la définition de \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 } } Vous pouvez voir à la toute fin que la méthode est invoquée. Jetons un coup d'œil à l'intérieur : register() <?php namespace Illuminate\Foundation; class AliasLoader { // ... protected $registered = false; public function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } } La variable est initialement définie sur . Par conséquent, nous entrons dans l’instruction et appelons la méthode . Explorons maintenant sa mise en œuvre. $registered false if prependToLoaderStack() // AliasLoader.php protected function prependToLoaderStack(): void { spl_autoload_register([$this, 'load'], true, true); } C'est ici que la magie opère ! Laravel appelle la fonction , une fonction PHP intégrée qui se déclenche lors de la tentative d'accès à une classe non définie. Il définit la logique à exécuter dans de telles situations. Dans ce cas, Laravel choisit d'invoquer la méthode lorsqu'il rencontre un appel non défini. spl_autoload_register() load() De plus, transmet automatiquement le nom de la classe non définie à la méthode ou à la fonction qu'elle appelle. spl_autoload_register() Explorons la méthode ; ça doit être la clé. 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); } } Nous vérifions si est défini et si quelle que soit la classe passée, dans notre cas, commence par tout ce qui est défini dans $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace La logique vérifie si est défini et si la classe passée, dans notre cas (qui n'est pas définie), commence par la valeur spécifiée dans $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace. // AliasLoader.php protected static $facadeNamespace = 'Facades\\'; Puisque nous avons préfixé l'espace de noms de notre contrôleur avec , satisfaisant la condition, nous procédons à Facades loadFacade() // AliasLoader.php protected function loadFacade($alias) { require $this->ensureFacadeExists($alias); } Ici, la méthode nécessite le chemin renvoyé par . La prochaine étape consiste donc à approfondir sa mise en œuvre. 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; } Tout d'abord, une vérification est effectuée pour déterminer si un fichier nommé existe. Dans notre cas, ce fichier n'est pas présent, déclenchant l'étape suivante : . Cette fonction crée un fichier et l'enregistre dans le spécifié. Le contenu du fichier est généré par , qui, à en juger par son nom, crée une façade à partir d'un stub. Si vous deviez inspecter , vous trouveriez ce qui suit : 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'; } } Cela vous semble familier ? C'est essentiellement ce que nous avons fait manuellement. Désormais, remplace le contenu factice par notre classe non définie après avoir supprimé le préfixe . Cette façade mise à jour est ensuite stockée. Par conséquent, lorsque requiert le fichier, il le fait correctement et finit par nécessiter le fichier suivant : 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'; } } Et maintenant, dans le flux habituel, nous demandons au conteneur d'application de renvoyer toute instance liée à la chaîne . Vous vous demandez peut-être si nous n'avons lié cette chaîne à rien, nous n'avons même pas touché à notre . Mais rappelez-vous ce que j’ai mentionné au tout début à propos du conteneur d’applications ? App\Http\Controllers\HelloWorld AppServiceProvider , mais à une condition, la classe ne doit pas avoir de constructeur. Sinon, il ne saurait pas comment le construire pour vous. Dans notre cas, notre classe n’a besoin d’aucun argument pour être construite. Ainsi, le conteneur le résout, le renvoie et tous les appels lui sont transmis par proxy. Même si la case est vide, elle renverra l'instance HelloWorld Récapitulatif des façades en temps réel : nous avons préfixé notre classe par . Lors du démarrage de l'application, Laravel enregistre , qui se déclenche lorsque nous appelons des classes non définies. Cela conduit finalement à la méthode . À l’intérieur , nous vérifions si la classe non définie actuelle est préfixée par . Cela correspond, alors Laravel essaie de le charger. Facades spl_autoload_register() load() load() Facades Comme la façade n'existe pas, il la crée à partir d'un stub et requiert ensuite le fichier. Et voilà ! Vous avez une façade classique, mais celle-ci a été créée à la volée. Plutôt cool, hein ? Conclusion Félicitations pour être arrivé jusqu'ici ! Je comprends que cela puisse être un peu écrasant. N'hésitez pas à revenir en arrière et à relire les sections qui n'ont pas vraiment cliqué pour vous. Le suivi de votre IDE peut également vous aider. Mais bon, fini la magie noire, ça doit faire du bien, du moins c'est ce que j'ai ressenti la première fois ! Et rappelez-vous, la prochaine fois que vous appellerez une méthode de manière statique, ce ne sera peut-être pas le cas 🪄