2019/03/21追記
app()->call([$this, 'hoge']);
でhoge
メソッドを呼び出せる
この中でBoundMethod::call
が叩かれる
結論
- 実装はIlluminate\Container\BoundMethod
-
Illuminate\Container\BoundMethod::call(app(), $callback);
で引数を自動注入できる。- $callbackは、[インスタンス, メソッド名] の配列を指定することで、メソッドも使用可。
-
例えばこんなとき
<?php
function fuga () { $hoge = app()->make(Hoge::class); $this->piyo($hoge); }
function piyo (Hoge $hoge) { /* … */ }
こう書ける
```php
<?php
function fuga () {
// 呼び出し側からhogeの4文字が消える
BoundMethod::call(app(), [$this, 'piyo']);
}
function piyo (Hoge $hoge) {
/* ... */
}
自動注入?
仮引数にタイプヒンティングをつけておくと、
実引数省略時、自動的にサービスコンテナを使って依存解決してくれるやつ
例
例えば、こんな風に書いたとき
routes/web.php
<?php
Route::get('/', 'HomeController@showIndex');
下記コントローラアクションが呼び出される。
App\Controller\HomeController.php
<?php
public function showIndex(Request $request)
{
/* ... */
}
Requestクラスの$requestインスタンスが自動的に依存解決され注入されているわけです
実装を追う
プログラム実行時に、プログラム自身を解析する … Reflection
/vendor/laravel/framework/src/Illuminateでreflection
でgrepをかけてみる
眺めていると…
これは…
<?php
Route::get('/', 'HomeController@showIndex');
この@
では?
ここから追ってみるぞー
Illuminate\Container\BoundMethod
ソースコードを追っていく。適宜コメントを付与した
protected static function callClass
<?php
// $container: サービスコンテナ。 app()
// $target: 'HomeController@showIndex' とかの文字列
protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
{
// 'クラス名@メソッド名' を'@'で分割し
$segments = explode('@', $target);
// メソッド名を拾う(なければ$defaultMethod)
$method = count($segments) == 2
? $segments[1] : $defaultMethod;
if (is_null($method)) {
throw new InvalidArgumentException('Method not provided.');
}
// このクラスのcallメソッドでメソッドを呼び出す
return static::call(
// クラス名を用いて、
// サービスコンテナからインスタンスを取り出している
$container, [$container->make($segments[0]), $method], $parameters
);
}
public static function call()
<?php
// callClassから呼ばれるやつ
// $container: サービスコンテナ。 app()
// $callback: 実行可能オブジェクト
// - string
// - array
public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
// $callbackがstringで、かつ
// '@'が含まれている場合、callClassに処理をゆだねる
// ('@'で分割されて、結局こっちに戻ってくる)
if (static::isCallableWithAtSign($callback) || $defaultMethod) {
return static::callClass($container, $callback, $parameters, $defaultMethod);
}
// $callbackがstringだが`@`が含まれない、
// または$callbackが配列形式の場合、ここにくる
return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
return call_user_func_array(
// dependenciesをgetしている。こいつが怪しいぞ
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
});
}
protected static function getMethodDependencies()
<?php
// メソッドの依存性を取得し、明示的に指定された引数とマージして返す
//
// $container: サービスコンテナ
// $callback: メソッド or 関数
// $parameters: 明示的に指定された実引数
// $dependencies: 指定省略された仮引数に対応する実引数をこれに入れていく
protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
$dependencies = [];
// Reflector!
// ゴールが見えてきた感じがする
//
// Reflectorにより仮引数情報を取得している
foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
}
return array_merge($dependencies, $parameters);
}
static::getCallReflector
は、ReflectionFunctionAbstractインタフェースの実装クラスのインスタンスを返すよう。
ReflectionFunctionAbstract::getParametersは、ReflectionParameterインスタンスを返す。
protected static function getCallReflector()
<?php
// $callbackに応じたReflectionFunctionAbstractを返す
// - メソッド: ReflectionMethod
// - 関数(クラスに属してないやつ) : ReflectionFunction
protected static function getCallReflector($callback)
{
if (is_string($callback) && strpos($callback, '::') !== false) {
$callback = explode('::', $callback);
}
return is_array($callback)
? new ReflectionMethod($callback[0], $callback[1])
: new ReflectionFunction($callback);
}
protected static function addDependencyForCallParameter()
<?php
// 依存性を $dependenciesに追加していく
//
// $contaimer: サービスコンテナ
// $parameter: リフレクションにより得られた仮引数情報
// &$parameters: 関数/メソッド呼び出しで明示的に指定された実引数
// &$dependencies: 指定省略された仮引数に対応。
// クラス名とサービスコンテナを用いてこれを埋めていく
protected static function addDependencyForCallParameter($container, $parameter, array &$parameters, &$dependencies)
{
// 明示的に実引数($parameters)に渡されてきたものは
// $parametersから$dependenciesに移す
if (array_key_exists($parameter->name, $parameters)) {
$dependencies[] = $parameters[$parameter->name];
unset($parameters[$parameter->name]);
// タイプヒンティングがある場合
} elseif ($parameter->getClass()) {
// サービスコンテナで解決し、追加
$dependencies[] = $container->make($parameter->getClass()->name);
// タイプヒンティングがない場合、
// 仮引数にデフォルト値が設定されていれば、それを用いる
} elseif ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
}
}
シーケンス図
STUB