前書き
バニラアイスが美味しい季節ですね。
さて、コントローラのコンストラクタにサービスクラスをDI(依存注入)して使うことってありますよね。
筆者もログ用のActivityLoggerをコントローラにDIして使おうとしていたのですが、ふと、$this->logger = $logger のようにコンストラクタで依存をプロパティへ代入するときに$thisの中身がどうやって決まるのか疑問に思いました。
そこでコンストラクタやメソッドの中に dd($this) を挿入して、インスタンス化の前後で$thisがどう変わるかを比較してみたので、その結果を記しておきます。
検証方法
下記のサービスクラスとコントローラークラスを用意しました。
<?php
namespace App\Services;
class ActivityLogger
{
public function log(string $message)
{
\Log::info('[Activity] ' . $message);
}
}
<?php
namespace App\Http\Controllers;
use App\Services\ActivityLogger;
class ProfileController extends Controller
{
protected $logger;
public function __construct(ActivityLogger $logger)
{
$this->logger = $logger;
}
public function show()
{
$this->logger->log('profile viewed');
return response()->json(['message' => 'profile']);
}
}
このコントローラの
- コンストラクタの最初(インスタンス化直後)
- show()実行時(インスタンス化+DI完了後)
にdd($this)を挿入して、出力結果を比較します。
<?php
namespace App\Http\Controllers;
use App\Services\ActivityLogger;
class ProfileController extends Controller
{
protected $logger;
public function __construct(ActivityLogger $logger)
{
// ①インスタンス化直後
$this->logger = $logger;
}
public function show()
{
// ②DI完了後
$this->logger->log('profile viewed');
return response()->json(['message' => 'profile']);
}
}
結果はいかが
loggerの中身を見てみると、インスタンス化直後はnullで、DI完了後はActivityLoggerのインスタンスがセットされています。
① インスタンス化直後の$this
App\Http\Controllers\ProfileController {#1013 ▼
#middleware: []
#logger: null
}
② DI完了後の$this
App\Http\Controllers\ProfileController {#1013 ▼
#middleware: []
#logger: App\Services\ActivityLogger {#1014}
}
なんで?
インスタンス化直後とDI完了後で$thisの値が上記のようになる理由は、処理が下記の流れで行われるからです。
① Laravelが空のProfileControllerをnewする
(プロパティは初期値の状態。loggerはまだnull)
↓
② コンストラクタが呼ばれる(コンストラクタ引数を解決)
(①のdd($this)が実行されるのはここ。空のコントローラが見える)
↓
③ DI(ActivityLoggerの解決)が行われ、$this->logger = $loggerによりActivityLoggerのインスタンスがプロパティへ代入される
(処理がコンストラクタ内部へ移る。コンストラクタ内部で初めてDIの値がセットされる)
↓
④ show()が呼ばれる(メソッド実行)
(②のdd($this)が実行されるのはここ。loggerがセットされた$thisが見える)
つまり、Laravelのサービスコンテナは「空インスタンス→依存解決→依存セット」の順で動くので、$thisは「コンストラクタ内で依存がセットされた後」に初めて完成するということですね。
ちなみに、インスタンス化直後に
- $this が初期状態のプロパティだけを持ったオブジェクトになっている理由
- $this->middleware が最初から [] として入っている理由
にも触れておきます。
1は、コントローラを生成するときに、サービスコンテナがコンストラクタの中身を安全に呼ぶため、インスタンスだけ先に作っているからです。
ReflectionClassでクラス情報を解析
↓
まずインスタンスだけ作る(依存はまだ解決しない)
↓
コンストラクタの引数をサービスコンテナで解決
↓
解決した引数を使ってコンストラクタを実行
2は、親クラス内にprotected $middleware = [];と書かれており、クラス定義レベルで初期化されているプロパティだからです。
まとめ
今回の検証で、下記のことがわかりました。
- コントローラがnewされた直後は、プロパティは初期値のまま
- DIの値が
$thisに入るのは、コンストラクタ内の代入が行われたタイミング - メソッド内で参照する
$thisは、DI によってプロパティがセットされた後のインスタンス
以上〜!
バニラアイスはMOWのやつが美味しかったのでおすすめです🍨