Sie haben gerade eine neue Laravel-Anwendung installiert, sie gestartet und die Willkommensseite erhalten. Wie alle anderen möchten Sie sehen, wie sie gerendert wird. Sie springen also in die Datei und stoßen auf diesen Code: web.php <?php use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); Es ist offensichtlich, wie wir die Willkommensansicht erhalten haben, aber Sie sind neugierig, wie der Router von Laravel funktioniert, also beschließen Sie, in den Code einzutauchen. Die anfängliche Annahme ist: Es gibt eine Klasse, für die wir eine statische Methode aufrufen. Wenn Sie jedoch darauf klicken, gibt es dort keine -Methode. Was für eine Art schwarzer Magie passiert also hier? Lassen Sie uns das entmystifizieren! Route get() get() Normale Fassaden Bitte beachten Sie, dass ich der Einfachheit halber die meisten PHPDocs entfernt und die Typen integriert habe. „…“ verweist auf weiteren Code. Ich empfehle dringend, Ihre IDE zu öffnen und dem Code zu folgen, um Verwirrung zu vermeiden. Lassen Sie uns unserem Beispiel folgen und die Klasse untersuchen. Route <?php namespace Illuminate\Support\Facades; class Route extends Facade { // ... protected static function getFacadeAccessor(): string { return 'router'; } } Hier gibt es nicht viel, nur die Methode , die den String zurückgibt. Unter Berücksichtigung dessen gehen wir nun zur übergeordneten Klasse über. 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); } } Innerhalb der übergeordneten Klasse gibt es viele Methoden, aber keine Methode. Aber es gibt eine interessante, die Methode. Es ist eine Methode, die aufgerufen wird, wenn eine undefinierte statische Methode, wie in unserem Fall , aufgerufen wird. Daher stellt unser Aufruf dar, was wir beim Aufruf übergeben haben, die Route und ein , das die Willkommensansicht zurückgibt. get() __callStatic() magische get() __callStatic('get', ['/', Closure()]) Route::get() / Closure() Wenn ausgelöst wird, versucht es zuerst, eine Variable durch Aufruf von zu setzen. Die enthält die eigentliche Klasse, an die der Aufruf weitergeleitet werden soll. Schauen wir uns das genauer an, es wird gleich Sinn ergeben __callStatic() $instance getFacadeRoot() $instance // Facade.php public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } Hey, schau mal, es ist von der untergeordneten Klasse , von der wir wissen, dass sie den String zurückgegeben hat. Dieser String wird dann an übergeben, das versucht, ihn in eine Klasse aufzulösen, eine Art Zuordnung, die besagt: „Welche Klasse repräsentiert dieser String?“ Mal sehen. 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]; } } Zuerst wird geprüft, ob ein statisches Array, , einen Wert mit dem angegebenen (der wiederum ist) hat. Wenn eine Übereinstimmung gefunden wird, wird einfach dieser Wert zurückgegeben. Dies ist Laravel-Caching, um die Leistung ein wenig zu optimieren. Dieses Caching erfolgt innerhalb einer einzelnen Anfrage. Wenn diese Methode innerhalb derselben Anfrage mehrmals mit demselben Argument aufgerufen wird, verwendet sie den zwischengespeicherten Wert. Nehmen wir an, es handelt sich um den ersten Aufruf und fahren fort. $resolvedInstance $name router Anschließend wird geprüft, ob gesetzt ist und eine Instanz des Anwendungscontainers ist $app $app // Facade.php protected static \Illuminate\Contracts\Foundation\Application $app; Wenn Sie wissen möchten, was ein Anwendungscontainer ist, stellen Sie sich ihn als eine Box vor, in der Ihre Klassen gespeichert sind. Wenn Sie diese Klassen benötigen, greifen Sie einfach in diese Box. Manchmal vollbringt dieser Container ein bisschen Magie. Selbst wenn die Box leer ist und Sie nach einer Klasse greifen, wird sie für Sie bereitgestellt. Das ist ein Thema für einen anderen Artikel. Jetzt fragen Sie sich vielleicht: „Wann wird festgelegt?“, denn das muss sein, sonst haben wir unsere nicht. Dieser Anwendungscontainer wird während des Bootstrapping-Prozesses unserer Anwendung festgelegt. Werfen wir einen kurzen Blick auf die Klasse : $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()); } } } Wenn eine Anfrage eingeht, wird sie an den Router gesendet. Kurz davor wird die Methode aufgerufen, die das Array verwendet, um die Anwendung vorzubereiten. Wenn Sie die Methode in der Klasse untersuchen, iteriert sie durch diese Bootstrapper und ruft deren Methode auf. bootstrap() bootstrappers bootstrapWith() \Illuminate\Foundation\Application bootstrap() Der Einfachheit halber konzentrieren wir uns nur auf , von dem wir wissen, dass es eine Methode enthält, die in aufgerufen wird. \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(); } } Und da ist es, wir legen den Anwendungscontainer für die Klasse mithilfe der statischen Methode Facade setFacadeApplication(). // RegisterFacades.php public static function setFacadeApplication($app) { static::$app = $app; } Sehen Sie, wir weisen die Eigenschaft zu, die wir innerhalb von testen. Dies beantwortet die Frage; fahren wir fort. $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]; } } Wir haben bestätigt, dass während des Anwendungs-Bootstrappings festgelegt wurde. Der nächste Schritt besteht darin, zu prüfen, ob die aufgelöste Instanz zwischengespeichert werden soll, indem überprüft wird, das standardmäßig auf true gesetzt ist. Schließlich rufen wir die Instanz aus dem Anwendungscontainer ab. In unserem Fall ist dies so, als würden wir bitten, eine beliebige Klasse bereitzustellen, die an die Zeichenfolge gebunden ist. $app $cached static::$app['router'] router Nun fragen Sie sich vielleicht, warum wir wie auf ein Array zugreifen, obwohl es eine Instanz des Anwendungscontainers und damit ein ist. Sie haben Recht! Der Anwendungscontainer implementiert jedoch eine PHP-Schnittstelle namens , die arrayähnlichen Zugriff ermöglicht. Wir können uns das einmal genauer ansehen, um dies zu bestätigen: $app Objekt ArrayAccess <?php namespace Illuminate\Container; use ArrayAccess; // <- this guy use Illuminate\Contracts\Container\Container as ContainerContract; class Container implements ArrayAccess, ContainerContract { // ... } Die gibt also tatsächlich eine Instanz zurück, die an den String gebunden ist, nämlich . Woher ich das wusste? Sehen Sie sich die Fassade an. Oft finden Sie ein PHPDoc , das darauf hinweist, was diese Fassade verbirgt oder genauer gesagt, an welche Klasse unsere Methodenaufrufe weitergeleitet werden. resolveFacadeInstance() router \Illuminate\Routing\Router Route @see Nun zurück zu unserer -Methode. __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); } } Wir haben , ein Objekt der Klasse . Wir testen, ob es gesetzt ist (was in unserem Fall bestätigt wird) und rufen die Methode direkt darauf auf. Das Ergebnis lautet also: $instance \Illuminate\Routing\Router // Facade.php return $instance->get('/', Closure()); Und jetzt können Sie bestätigen, dass in der Klasse vorhanden ist. 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); } } Damit ist es erledigt! War das am Ende nicht schwierig? Um es noch einmal zusammenzufassen: Eine Fassade gibt einen String zurück, der an den Container gebunden ist. Beispielsweise könnte an die Klasse gebunden sein. Wenn wir eine undefinierte Methode auf einer Fassade statisch aufrufen, beispielsweise , greift ein. hello-world HelloWorld HelloWorldFacade __callStatic() Es löst den in seiner -Methode registrierten String in das auf, was im Container gebunden ist, und leitet unseren Aufruf an die abgerufene Instanz weiter. Somit erhalten wir . Das ist das Wesentliche! Hat es bei Ihnen immer noch nicht geklickt? Dann erstellen wir unsere Fassade! getFacadeAccessor() (new HelloWorld())->method() Lasst uns unsere Fassade gestalten Angenommen, wir haben diese Klasse: <?php namespace App\Http\Controllers; class HelloWorld { public function greet(): string { return "Hello, World!"; } } Das Ziel besteht darin aufzurufen. Dazu binden wir unsere Klasse an den Anwendungscontainer. Navigieren Sie zunächst zu . 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; }); } // ... } Wenn wir jetzt von unserem Anwendungscontainer (oder der Box, wie ich bereits erwähnt habe) anfordern, gibt dieser eine Instanz von “ zurück. Was bleibt übrig? Erstellen Sie einfach eine Fassade, die die Zeichenfolge zurückgibt. 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'; } } Wenn dies vorhanden ist, können wir es verwenden. Rufen wir es in unserer web.php. <?php use App\Http\Facades; use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorldFacade::greet(); // Hello, World! }); Wir wissen, dass auf der Fassade nicht existiert, wird ausgelöst. Es zieht eine durch einen String dargestellte Klasse (in unserem Fall ) aus dem Anwendungscontainer. Und wir haben diese Bindung bereits im vorgenommen; wir haben ihn angewiesen, eine Instanz von bereitzustellen, wenn jemand anfordert. Folglich wird jeder Aufruf, wie beispielsweise , auf diese abgerufene Instanz von angewendet. Und das war’s. greet() HelloWorldFacade __callStatic() hello-world AppServiceProvider HelloWorld hello-world greet() HelloWorld Herzlichen Glückwunsch! Sie haben Ihre ganz persönliche Fassade erstellt! Laravel Echtzeitfassaden Nachdem Sie nun ein gutes Verständnis von Fassaden haben, können wir Ihnen noch einen weiteren Zaubertrick enthüllen. Stellen Sie sich vor, Sie könnten aufrufen, ohne eine Fassade zu erstellen, und zwar mithilfe von . HelloWorld::greet() Echtzeit-Fassaden Werfen wir einen Blick: <?php use Facades\App\Http\Controllers; // Notice the prefix use Illuminate\Support\Facades\Route; Route::get('/', function () { return HelloWorld::greet(); // Hello, World! }); Indem wir dem Namespace des Controllers voranstellen, erzielen wir dasselbe Ergebnis wie zuvor. Es ist jedoch sicher, dass der Controller keine statische Methode mit dem Namen hat! Und woher kommt überhaupt? Ich verstehe, dass dies wie Zauberei erscheinen mag, aber wenn man es einmal verstanden hat, ist es ganz einfach. Facades HelloWorld greet() Facades\App\Http\Controllers\HelloWorld Schauen wir uns die genauer an, die wir zuvor überprüft haben. Es handelt sich um die Klasse, die für die Festlegung von \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 } } Ganz am Ende sieht man, dass die Methode aufgerufen wird. Werfen wir einen Blick hinein: register() <?php namespace Illuminate\Foundation; class AliasLoader { // ... protected $registered = false; public function register(): void { if (! $this->registered) { $this->prependToLoaderStack(); $this->registered = true; } } } Die Variable ist zunächst auf gesetzt. Daher geben wir die Anweisung ein und rufen die Methode auf. Sehen wir uns nun ihre Implementierung an. $registered false if prependToLoaderStack() // AliasLoader.php protected function prependToLoaderStack(): void { spl_autoload_register([$this, 'load'], true, true); } Hier geschieht die Magie! Laravel ruft die Funktion auf, eine integrierte PHP-Funktion, die ausgelöst wird, wenn versucht wird, auf eine undefinierte Klasse zuzugreifen. Sie definiert die Logik, die in solchen Situationen ausgeführt werden soll. In diesem Fall ruft Laravel die Methode auf, wenn ein undefinierter Aufruf auftritt. spl_autoload_register() load() Darüber hinaus übergibt den Namen der nicht definierten Klasse automatisch an die Methode oder Funktion, die es aufruft. spl_autoload_register() Lassen Sie uns die Methode untersuchen. Sie muss der Schlüssel sein. 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); } } Wir prüfen, ob festgelegt ist und ob die übergebene Klasse, in unserem Fall mit dem beginnt, was in festgelegt ist. $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace Die Logik prüft, ob festgelegt ist und ob die übergebene Klasse, in unserem Fall (die nicht definiert ist), mit dem in $facadeNamespace Facades\App\Http\Controllers\HelloWorld $facadeNamespace. // AliasLoader.php protected static $facadeNamespace = 'Facades\\'; Da wir dem Namespace unseres Controllers vorangestellt haben und damit die Bedingung erfüllen, fahren wir mit fort. Facades loadFacade() // AliasLoader.php protected function loadFacade($alias) { require $this->ensureFacadeExists($alias); } Hier erfordert die Methode den Pfad, der von zurückgegeben wird. Der nächste Schritt besteht also darin, sich mit der Implementierung zu befassen. 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; } Zunächst wird geprüft, ob eine Datei namens existiert. In unserem Fall ist diese Datei nicht vorhanden, was den nächsten Schritt auslöst: . Diese Funktion erstellt eine Datei und speichert sie im angegebenen . Der Inhalt der Datei wird von generiert, das, dem Namen nach zu urteilen, eine Fassade aus einem Stub erstellt. Wenn Sie untersuchen würden, würden Sie Folgendes finden: 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'; } } Kommt Ihnen das bekannt vor? Das ist im Wesentlichen das, was wir manuell gemacht haben. Nun ersetzt den Dummy-Inhalt durch unsere undefinierte Klasse, nachdem das Präfix entfernt wurde. Diese aktualisierte Fassade wird dann gespeichert. Wenn die Datei benötigt, tut es dies daher korrekt und benötigt am Ende die folgende Datei: 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'; } } Und jetzt bitten wir im üblichen Ablauf den Anwendungscontainer, jede Instanz zurückzugeben, die an die Zeichenfolge gebunden ist. Sie fragen sich vielleicht, wir haben diese Zeichenfolge an nichts gebunden, wir haben unseren nicht einmal berührt. Aber erinnern Sie sich, was ich ganz am Anfang über den Anwendungscontainer gesagt habe? App\Http\Controllers\HelloWorld AppServiceProvider , allerdings unter einer Bedingung: Die Klasse darf keinen Konstruktor haben. Andernfalls wüsste sie nicht, wie sie diese für Sie erstellen soll. In unserem Fall benötigt unsere Klasse keine Argumente, um erstellt zu werden. Der Container löst sie also auf, gibt sie zurück und alle Aufrufe werden an sie weitergeleitet. Auch wenn das Feld leer ist, wird die Instanz zurückgegeben HelloWorld Zusammenfassung zu Echtzeit-Fassaden: Wir haben unserer Klasse vorangestellt. Während des Anwendungs-Bootstrappings registriert Laravel , das ausgelöst wird, wenn wir undefinierte Klassen aufrufen. Es führt schließlich zur Methode . Innerhalb von prüfen wir, ob die aktuelle undefinierte Klasse vorangestellt hat. Das stimmt überein, also versucht Laravel, sie zu laden. Facades spl_autoload_register() load() load() Facades Da die Fassade nicht existiert, wird sie aus einem Stub erstellt und dann die Datei angefordert. Und voilà! Sie haben eine normale Fassade, aber diese wurde spontan erstellt. Ziemlich cool, oder? Abschluss Herzlichen Glückwunsch, dass Sie es so weit geschafft haben! Ich verstehe, dass es ein wenig überwältigend sein kann. Gehen Sie ruhig zurück und lesen Sie alle Abschnitte, die bei Ihnen nicht ganz gezündet haben, noch einmal durch. Auch die Nachverfolgung Ihrer IDE kann hilfreich sein. Aber hey, keine schwarze Magie mehr, muss sich gut anfühlen, zumindest habe ich mich beim ersten Mal so gefühlt! Und denken Sie daran, dass dies beim nächsten statischen Aufruf einer Methode möglicherweise nicht der Fall ist 🪄