新しくLaravelアプリケーションをインストールし、起動してウェルカムページが表示されました。他の皆さんと同じように、どのように表示されるか確認したいので、 web.php ファイルを開くと次のコードが表示されます:
<?php use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
ウェルカムビューの取得方法は明らかですが、Laravelのルーターがどのように動作するのか興味があるので、コードを調べることにしました。最初の仮定は、 Route 静的メソッドを呼び出すクラス get()しかし、クリックしても get() そこには方法があります。それでは、どのような黒魔術が起こっているのでしょうか? これを解明してみましょう!
通常のファサード
簡潔にするために、PHPDoc のほとんどを削除し、型をインライン化したことに注意してください。「…」は、追加のコードを指します。
混乱を避けるために、IDE を開いてコードに沿って進むことを強くお勧めします。
私たちの例に従って、 Route クラス。
<?php namespace Illuminate\Support\Facades;
class Route extends Facade
{
// ...
protected static function getFacadeAccessor(): string
{
return 'router';
}
}
ここには大したことはないけど、 getFacadeAccessor() 文字列を返すメソッド routerこれを念頭に置いて、親クラスに移りましょう。
$method(...$args);
}
}
親クラスにはたくさんのメソッドがあり、 get() 方法があります。しかし、興味深い方法があります。 __callStatic() 方法です。 魔法 メソッドは、定義されていない静的メソッド、例えば get() 我々の場合、は呼ばれます。したがって、我々の呼び出しは __callStatic('get', ('/', Closure())) 呼び出し時に渡したものを表します Route::get()、 ルート / そして Closure() ウェルカムビューを返します。
いつ __callStatic() トリガーされると、まず変数を設定しようとする $instance 電話をかける getFacadeRoot()、 $instance 呼び出しを転送する実際のクラスを保持します。詳しく見てみましょう。すぐに意味がわかります。
// Facade.php
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
おい、見てよ 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);
}
}
まず静的配列かどうかをチェックし、 $resolvedInstanceは、指定された値を持つ $name (これもまた router) 。一致するものが見つかった場合は、その値を返します。これは、パフォーマンスを少し最適化するための Laravel のキャッシュです。このキャッシュは、単一のリクエスト内で行われます。このメソッドが同じリクエスト内で同じ引数で複数回呼び出された場合は、キャッシュされた値が使用されます。これが最初の呼び出しであると仮定して、先に進みましょう。
次に、 $app が設定され、 $app アプリケーションコンテナのインスタンスである
// Facade.php
protected static \Illuminate\Contracts\Foundation\Application $app;
アプリケーション コンテナーがどのようなものか知りたい場合は、クラスが格納されているボックスと考えてください。クラスが必要な場合は、そのボックスに手を伸ばすだけです。このコンテナーは、時々ちょっとした魔法をかけてくれます。ボックスが空であっても、クラスを取ろうと手を伸ばすと、自動的に取得されます。これは別の記事で取り上げるトピックです。
さて、あなたは「 $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, // app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
}
リクエストが届くと、それはルーターに送られます。その直前に、 bootstrap() メソッドが呼び出され、 bootstrappers アプリケーションを準備するための配列。 bootstrapWith() 方法 \Illuminate\Foundation\Application クラスでは、これらのブートストラッパーを反復処理して、 bootstrap() 方法。
簡単にするために、 \Illuminate\Foundation\Bootstrap\RegisterFacades、これには bootstrap() 呼び出されるメソッド bootstrapWith()
make('config')->get('app.aliases', ()),
$app->make(PackageManifest::class)->aliases()
))->register();
}
}
これで、アプリケーションコンテナを Facade 静的メソッドを使用するクラス setFacadeApplication().
// RegisterFacades.php
public static function setFacadeApplication($app)
{
static::$app = $app;
}
ほら、私たちは割り当てます $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);
}
}
我々は確認した $app アプリケーションのブートストラップ中に設定されます。次のステップは、解決されたインスタンスをキャッシュする必要があるかどうかを確認することです。 $cachedデフォルトではtrueです。最後に、アプリケーションコンテナからインスタンスを取得します。この場合、次のように尋ねます。 static::$app('router') 文字列にバインドされた任意のクラスを提供する router。
さて、なぜアクセスするのか疑問に思うかもしれません $app アプリケーションコンテナのインスタンスであるにもかかわらず、配列のように 物体そうですね、その通りです!しかし、アプリケーションコンテナはPHPインターフェースを実装しています。 ArrayAccess配列のようなアクセスが可能になります。この事実を確認するために、以下を見てみましょう。
<?php namespace Illuminate\Container;
use ArrayAccess; // <- this guy
use Illuminate\Contracts\Container\Container as ContainerContract;
class Container implements ArrayAccess, ContainerContract {
// ...
}
だから、 resolveFacadeInstance() 実際にバインドされたインスタンスを返します router 文字列、具体的には、 \Illuminate\Routing\Routerどうして分かったのか? Route ファサード;多くの場合、PHPDoc @see このファサードが何を隠しているか、より正確には、メソッド呼び出しがどのクラスにプロキシされるかを示唆します。
さて、私たちの __callStatic 方法。
$method(...$args);
}
}
我々は持っています $instance、のオブジェクト \Illuminate\Routing\Router クラス。設定されているかどうかをテストし (この場合は確認済み)、そのメソッドを直接呼び出します。つまり、次のようになります。
// Facade.php
return $instance->get('/', Closure());
そして今、あなたは get() 内に存在する \Illuminate\Routing\Router クラス。
addRoute(('GET', 'HEAD'), $uri, $action);
}
}
これで終わりです。結局、難しくなかったですか? まとめると、ファサードはコンテナにバインドされた文字列を返します。たとえば、 hello-world に縛られるかもしれない HelloWorld クラス。ファサードで未定義のメソッドを静的に呼び出すと、 HelloWorldFacade 例えば、 __callStatic() 介入する。
登録された文字列を解決し、 getFacadeAccessor() メソッドをコンテナ内にバインドされているものに渡し、取得したインスタンスへの呼び出しをプロキシします。 (new HelloWorld())->method()それが本質です!まだピンと来ませんか?それではファサードを作成してみましょう!
ファサードを作ろう
次のようなクラスがあるとします。
<?php namespace App\Http\Controllers;
class HelloWorld
{
public function greet(): string {
return "Hello, World!";
}
}
目標は、 HelloWorld::greet()これを行うには、クラスをアプリケーションコンテナにバインドします。まず、 AppServiceProvider。
app->bind('hello-world', function ($app) {
return new HelloWorld;
});
}
// ...
}
今では、私たちがリクエストするたびに 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';
}
}
これで準備は完了です。 web.php.
<?php use App\Http\Facades;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return HelloWorldFacade::greet(); // Hello, World!
});
私達はことを知っています greet() 存在しない HelloWorldFacade ファサード、 __callStatic() がトリガーされます。文字列で表されるクラスを取得します(hello-world このバインディングは既にアプリケーションコンテナから作成されています。 AppServiceProvider; インスタンスを提供するように指示しました HelloWorld 誰かがリクエストするたびに hello-worldしたがって、 greet()は、取得したインスタンスに対して操作を行います。 HelloWorld。 以上です。
おめでとうございます!独自のファサードを作成しました。
Laravel リアルタイム ファサード
ファサードについてよく理解できたところで、もう1つ魔法のトリックを紹介します。 HelloWorld::greet() ファサードを作成せずに、リアルタイム ファサードを使用します。
みてみましょう:
<?php use Facades\App\Http\Controllers; // Notice the prefix
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return HelloWorld::greet(); // Hello, World!
});
コントローラの名前空間にプレフィックスを付けることで Facades、先ほどと同じ結果が得られます。しかし、 HelloWorld コントローラには静的メソッドがありません greet()!そして、 Facades\App\Http\Controllers\HelloWorld どこから来たのか?これは魔法のように思えるかもしれないが、一度理解してしまえば、非常に簡単なことだ。
詳しく見てみましょう \Illuminate\Foundation\Bootstrap\RegisterFacades 先ほど確認したように、 $app:
make('config')->get('app.aliases', ()),
$app->make(PackageManifest::class)->aliases()
))->register(); // Interested here
}
}
最後に、 register() メソッドが呼び出されます。中身を見てみましょう:
registered) {
$this->prependToLoaderStack();
$this->registered = true;
}
}
}
の $registered 変数は最初に falseしたがって、 if 声明を出し、 prependToLoaderStack() メソッドです。では、その実装を見てみましょう。
// AliasLoader.php
protected function prependToLoaderStack(): void
{
spl_autoload_register(($this, 'load'), true, true);
}
ここで魔法が起こります!Laravelは spl_autoload_register() 関数は、未定義のクラスにアクセスしようとしたときに呼び出される組み込みのPHP関数です。これは、そのような状況で実行するロジックを定義します。この場合、Laravelは load() 未定義の呼び出しに遭遇したときにメソッドを実行します。
さらに、 spl_autoload_register() 呼び出されるメソッドまたは関数に、未定義のクラスの名前が自動的に渡されます。
探検してみましょう 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);
}
}
確認します $facadeNamespace が設定されており、どのクラスが合格しても、この場合は Facades\App\Http\Controllers\HelloWorld 設定されたものから始まる $facadeNamespace
ロジックは以下をチェックします $facadeNamespace が設定されており、渡されたクラス(この場合は Facades\App\Http\Controllers\HelloWorld (未定義)は、 $facadeNamespace.
// AliasLoader.php
protected static $facadeNamespace = 'Facades\\';
コントローラの名前空間に接頭辞を付けたので Facades条件を満たすので、次に進みましょう。 loadFacade()
// AliasLoader.php
protected function loadFacade($alias)
{
require $this->ensureFacadeExists($alias);
}
ここで、メソッドは、 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;
}
まず、次の名前のファイルが 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';
}
}
見覚えがありますか?これは基本的に手動で行ったことです。 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';
}
}
そして、通常のフローでは、アプリケーションコンテナに文字列にバインドされたインスタンスを返すように要求します。 App\Http\Controllers\HelloWorld不思議に思うかもしれませんが、この文字列は何もバインドされておらず、 AppServiceProviderしかし、冒頭でアプリケーション コンテナーについて述べたことを覚えていますか?
ボックスが空であってもインスタンスを返しますただし、クラスにはコンストラクタがあってはなりません。そうでなければ、クラスはそれをどのように構築すればよいかわかりません。私たちの場合、 HelloWorld クラスの構築には引数は必要ありません。そのため、コンテナはそれを解決して返し、すべての呼び出しはそれにプロキシされます。
リアルタイムファサードの要約: クラスにプレフィックスを付けました Facadesアプリケーションのブートストラップ中に、Laravelは spl_autoload_register()未定義のクラスを呼び出すと発生します。これは最終的に load() 方法。内部 load()、現在の未定義のクラスにプレフィックスが付いているかどうかを確認します。 Facades一致するので、Laravel はそれをロードしようとします。
ファサードが存在しないため、スタブからファサードを作成し、ファイルを要求します。すると、出来上がりです。通常のファサードがありますが、これはオンザフライで作成されました。かなりクールですよね?
結論
ここまでお読みいただき、ありがとうございます。少し圧倒されるかもしれませんね。理解できなかったセクションは、戻ってもう一度読んでみてください。IDE でフォローするのも役立ちます。でも、黒魔術はもうやめて、気分がいいに違いありません。少なくとも、最初はそう感じました。
そして、次にメソッドを静的に呼び出すときには、そうではないかもしれないことを覚えておいてください🪄