Symfony\Component\Console\Command\Command::run() から Illuminate/Console/Command::execute() がコールされました。見てみましょう。
Illuminate/Console/Command::execute()
/**
* Execute the console command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
return (int) $this->laravel->call([$this, 'handle']);
}
Symfony\Component\Console\Input\InputInterface インスタンスで入力です。第三引数は
Symfony\Component\Console\Output\OutputInterface インスタンスで出力です。戻り値は整数です。
コマンドアプリケーションコンテナの call() を引数に自身と 「handle」を配列にして渡しコールしています。しばらく Symfony だったので Laravel側は久しぶりですね。見てみましょう。
Illuminate\Container\Container::call()
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public function call($callback, array $parameters = [], $defaultMethod = null)
{
return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}
Illuminate\Container\BoundMethod::call()
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param \Illuminate\Container\Container $container
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*
* @throws \ReflectionException
* @throws \InvalidArgumentException
*/
public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
if (static::isCallableWithAtSign($callback) || $defaultMethod) {
return static::callClass($container, $callback, $parameters, $defaultMethod);
}
return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
return call_user_func_array(
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
});
}
/**
* Determine if the given string is in Class@method syntax.
*
* @param mixed $callback
* @return bool
*/
protected static function isCallableWithAtSign($callback)
{
return is_string($callback) && strpos($callback, '@') !== false;
}
/**
* Call a string reference to a class using Class@method syntax.
*
* @param \Illuminate\Container\Container $container
* @param string $target
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
{
$segments = explode('@', $target);
// We will assume an @ sign is used to delimit the class name from the method
// name. We will split on this @ sign and then build a callable array that
// we can pass right back into the "call" method for dependency binding.
$method = count($segments) === 2
? $segments[1] : $defaultMethod;
if (is_null($method)) {
throw new InvalidArgumentException('Method not provided.');
}
return static::call(
$container, [$container->make($segments[0]), $method], $parameters
);
}
Illuminate\Container\Container::call()
第二引数は配列型でコールバック関数に渡す引数です。
第三引数はストリング若しくは
null でデフォルトのメソッドです。戻り値は
mix です。
自身と受け取った引数をそのまま BoundMethod::call() メソッドにスタティックコールして戻り値をそのまま戻しています。
Illuminate\Container\BoundMethod::call()
第二引数はコールバック関数若しくはストリング型です。
第三引数は配列型でコールバック関数に渡す引数です。
第四引数はストリング若しくは
null でデフォルトのメソッドです。戻り値は
mix です。
isCallableWithAtSign() メソッドを引数にコールバック関数を渡した戻り値が true 若しくはデフォルトメソッドが引数として渡されている場合は callClass メソッドを引数そのまま渡してコールします。
isCallableWithAtSign() メソッドは コールバック関数として渡された第一引数がストリング型で且つ「@」が含まれている場合 true を戻します。これは、「クラス名@メソッド名」という形でコールバックする関数を指定する事ができるようにしています。
callClass メソッドは、コールバック関数として受け取ったストリングを 「@」で explode() し、$segments に代入します。$segments の配列の数が2ならば一番目、そうでなければデフォルトメソッドを $method に代入します。
$method が null ならば「Method not provided.」とメッセージを添えて例外 InvalidArgumentException をスローします。
コマンドアプリケーションコンテナに $segments[0] と $method を使い make し、それをコールバック関数として call() メソッドをコールします。
isCallableWithAtSign() の戻り値が false だった場合、callBoundMethod() メソッドをコマンドアプリケーションとコールバック関数とクロージャーを引数に渡しコールします。
渡されているクロージャーを見てみましょう。
function () use ($container, $callback, $parameters) {
return call_user_func_array(
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
}
Illuminate\Container\BoundMethod::getMethodDependencies() | 関連メソッド
/*
* Get all dependencies for a given method.
*
* @param \Illuminate\Container\Container $container
* @param callable|string $callback
* @param array $parameters
* @return array
*
* @throws \ReflectionException
*/
protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
$dependencies = [];
foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
}
return array_merge($dependencies, $parameters);
}
/**
* Get the proper reflection instance for the given callback.
*
* @param callable|string $callback
* @return \ReflectionFunctionAbstract
*
* @throws \ReflectionException
*/
protected static function getCallReflector($callback)
{
if (is_string($callback) && strpos($callback, '::') !== false) {
$callback = explode('::', $callback);
} elseif (is_object($callback) && ! $callback instanceof Closure) {
$callback = [$callback, '__invoke'];
}
return is_array($callback)
? new ReflectionMethod($callback[0], $callback[1])
: new ReflectionFunction($callback);
}
/**
* Get the dependency for the given call parameter.
*
* @param \Illuminate\Container\Container $container
* @param \ReflectionParameter $parameter
* @param array $parameters
* @param array $dependencies
* @return void
*/
protected static function addDependencyForCallParameter($container, $parameter,
array &$parameters, &$dependencies)
{
if (array_key_exists($parameter->name, $parameters)) {
$dependencies[] = $parameters[$parameter->name];
unset($parameters[$parameter->name]);
} elseif ($parameter->getClass() && array_key_exists($parameter->getClass()->name, $parameters)) {
$dependencies[] = $parameters[$parameter->getClass()->name];
unset($parameters[$parameter->getClass()->name]);
} elseif ($parameter->getClass()) {
$dependencies[] = $container->make($parameter->getClass()->name);
} elseif ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
}
}
call_user_func_array() してます。引数はコールバック関数と getMethodDependencies() です。
まずは getMethodDependencies() メソッドを見てみましょう。
getCallReflector() メソッドの戻り値の getParameters() を foreach してます。
getCallReflector()メソッドを見てみましょう。
コールバックがストリング型で「::」を含む場合は、「::」 で explode() した結果の配列を $callback に代入します。
コールバックがクロージャーの場合は、それを0番目、1番目に「__invoke」を入れた配列を $callback に代入します。
コールバックが配列だった場合は、ReflectionMethod インスタンスを引数にコールバック配列の0番目と1番目を渡して生成して返します。
そうでなかった場合は ReflectionFunction インスタンスを引数にコールバックを渡して生成したものを戻します。
今回は「[$this, ‘handle’]」が入っていますので、 ReflectionMethod が生成されます。
ReflectionMethod インスタンス及び ReflectionFunction インスタンスの getParameters() メソッドは共にパラメータを ReflectionParameter オブジェクトの配列で返します。
つまり、ReflectionParameter オブジェクトの配列を foreach で回すわけですね。
foreach の中で、addDependencyForCallParameter() メソッドを コマンドアプリケーションコンテナ、ReflectionParameter オブジェクトの配列の中身、参照渡しでコマンドの引数、そして参照渡しで依存関係配列を引数として渡しコールします。
addDependencyForCallParameter() メソッドを見てみましょう。
$parameter->name は ReflectionParameter クラスで $name は読込専用でパラメータ名が格納されています。つまり、コマンド引数のパラメーターの中にコールバック関数のパラメータ名がキーとなる値が存在していた場合、依存関係配列にパラメーター名をキーとした配列の値を追加し、同じくパラメーター名をキーとしたコマンド引数の配列を unset で削除します。
そうでない場合、
ReflectionParameter::getClass() メソッドはタイプヒント付きのクラスを取得します。取得したクラスは ReflectionClass インスタンスで $name はクラス名です。
依存関係にコマンド引数のクラス名をキーとした値を追加し、同じくクラス名をキーとしたコマンド引数の配列を unset で削除します。
そうでない場合、
$parameter->getClass() でクラス名が取得できた場合は、コマンドアプリケーションコンテナで $parameter->getClass()->name で取得したクラス名で make() したものを依存関係配列に追加します。
そうでない場合、$parameter->isDefaultValueAvailable() でデフォルト値が存在するか調べています。
もし、存在する場合は依存関係に $parameter->getDefaultValue() でデフォルト値を取得したものを追加します。
ReflectionParameter 等の詳細は php.net を参照してください。
依存関係の解決の処理のようです。ここらへんは複雑になってきましたので、追いきれていない感じがします。
別の機会を作ってもう少し整理したいと思います。
続きを読みましょう。
addDependencyForCallParameter() メソッドで依存関係を調べて戻ってきた配列とコマンド引数配列を array_merge で結合して戻したものが call_user_func_array() で実行されます。
もう一度流れを見てみましょう。
Illuminate/Console/Command::execute()
/**
* Execute the console command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
return (int) $this->laravel->call([$this, 'handle']);
}
Illuminate\Container\Container::call()
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public function call($callback, array $parameters = [], $defaultMethod = null)
{
return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}
Illuminate\Container\BoundMethod::call() | 抜粋
function () use ($container, $callback, $parameters) {
return call_user_func_array(
$callback, static::getMethodDependencies($container, $callback, $parameters)
);
}
call_user_func_array() の use に渡される変数は、 $container がコマンドアプリケーションコンテナ、$callback が [$this, 'handle']、$parameters が [] ですね。
[$this, 'handle'] に対して static::getMethodDependencies($container, $callback, $parameters) を引数に渡してコールします。
getMethodDependencies() は getCallReflector() でコールバックを使い、new ReflectionMethod($callback[0], $callback[1]) をで ReflectionMethod を生成しています。つまり、MigrateCommand::handle() がコールされる結果となります。
いよいよ MigrateCommand::handle() を読むろころまで来ました。
その13に続きます。