Kernel インスタンスに引数として Symfony\Component\Console\Input\ArgvInput と Symfony\Component\Console\Output\ConsoleOutput を渡して handle() メソッドがコールされました。
handle() メソッドを見てみましょう。
Illuminate\Foundation\Console\Kernel::handle()
/**
* Run the console application.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface|null $output
* @return int
*/
public function handle($input, $output = null)
{
try {
$this->bootstrap();
return $this->getArtisan()->run($input, $output);
} catch (Throwable $e) {
$this->reportException($e);
$this->renderException($output, $e);
return 1;
}
}
/**
* Get the Artisan application instance.
*
* @return \Illuminate\Console\Application
*/
protected function getArtisan()
{
if (is_null($this->artisan)) {
return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version()))
->resolveCommands($this->commands);
}
return $this->artisan;
}
Illuminate\Foundation\Console\Kernel::handle()
Symfony\Component\Console\Input\ArgvInput インスタンスです。第二引数は
Symfony\Component\Console\Output\ConsoleOutput インスタンスです。戻り値は整数型です。
まず bootstrap() メソッドがコールされます。ここは以前読みましたのでもう理解できています。
次に getArtisan() メソッドがコールされます。
getArtisan() メソッドの戻り値は Illuminate\Console\Application インスタンスです。
getArtisan() メソッドは $this->artisan が null かどうか検証します。nullでなければそれを返します。null ならば、アプリケーション、イベント、バージョンを渡し Artisan インスタンスを生成し、resolveCommands() を実行した戻り値を返します。
Artisan の実体は Illuminate\Console\Application です。見てみましょう。
Illuminate\Console\Application::__construct()
/**
* Create a new Artisan console application.
*
* @param \Illuminate\Contracts\Container\Container $laravel
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @param string $version
* @return void
*/
public function __construct(Container $laravel, Dispatcher $events, $version)
{
parent::__construct('Laravel Framework', $version);
$this->laravel = $laravel;
$this->events = $events;
$this->setAutoExit(false);
$this->setCatchExceptions(false);
$this->events->dispatch(new ArtisanStarting($this));
$this->bootstrap();
}
/**
* Bootstrap the console application.
*
* @return void
*/
protected function bootstrap()
{
foreach (static::$bootstrappers as $bootstrapper) {
$bootstrapper($this);
}
}
Symfony\Component\Console\Application::__construct() | 関連メソッド
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->terminal = new Terminal();
$this->defaultCommand = 'list';
}
/**
* Sets whether to automatically exit after a command execution or not.
*/
public function setAutoExit(bool $boolean)
{
$this->autoExit = $boolean;
}
/**
* Sets whether to catch exceptions or not during commands execution.
*/
public function setCatchExceptions(bool $boolean)
{
$this->catchExceptions = $boolean;
}
Illuminate\Console\Application::__construct()
第二引数はイベントディスパッチャーです。
第三引数はストリング型でバージョン番号です。
戻り値はありません。
まず、スーパークラス Symfony\Component\Console\Application のコンストラクタをアプリケーション名「Laravel Framework」とバージョン情報を引数にコールします。
Symfony\Component\Console\Application のコンストラクタは受け取ったアプリケーション名とバージョン名を変数に代入し、$this->terminal に Terminal インスタンスを生成したものを代入し、デフォルトコマンドをセットします。Terminal にコンストラクタはありません。保持ファンクションの一覧を見てみましょう。
Symfony\Component\Console\Terminal 変数 | ファンクション一覧
private static $width;
private static $height;
private static $stty;
public function getWidth()
public function getHeight()
private static function initDimensions()
private static function hasVt100Support(): bool
private static function initDimensionsUsingStty()
private static function getConsoleMode(): ?array
private static function getSttyColumns(): ?string
private static function readFromProcess(string $command): ?string
Symfony\Component\Console\Terminal
縦幅横幅の取得、Sttyが利用可能か、サイズの初期化、VT100をサポートしているか、Stty利用時のサイズ初期化、mode CON が使用可能か(コマンドプロンプトウィンドウの変更)。Sttyの行数の取得、コマンドの実行、等のメソッドを実装しているようです。端末に関することを担うクラスのようです。
Illuminate\Console\Application::__construct() に戻ります。
アプリケーションコンテナとイベントディスパッチャーを変数に代入します。
setAutoExit() メソッドを false を渡しコールします。
setAutoExit() メソッドは $this->autoExit をセットします。
コマンド実行後に自動的に終了するかどうかを設定するパラメータのようです。
setCatchExceptions() メソッドを false を渡しコールします。
setCatchExceptions() メソッドは $this->catchExceptions をセットします。
コマンドの実行中に例外をキャッチするかどうかを設定するパラメータのようです。
次にイベントディスパッチャーに ArtisanStarting を引数に $this を渡して生成したものを登録します。
ArtisanStarting クラスは非常に小さな定義のクラスです。
Illuminate\Console\Events\ArtisanStarting
<?php
namespace Illuminate\Console\Events;
class ArtisanStarting
{
/**
* The Artisan application instance.
*
* @var \Illuminate\Console\Application
*/
public $artisan;
/**
* Create a new event instance.
*
* @param \Illuminate\Console\Application $artisan
* @return void
*/
public function __construct($artisan)
{
$this->artisan = $artisan;
}
}
コンストラクタでコンソールアプリケーションを受け取り $this->artisan に代入します。
Illuminate\Console\Application::__construct() の続きです。
bootstrap() メソッドをコールしています。
bootstrap() メソッドは、foreach で static::$bootstrappers を回して $bootstrapper() を引数に自身を渡してコールしています。 static::$bootstrappers は生成の過程で仕込まれている様子はありませんでした。おそらく Kernel が初期化される工程で準備されるのでしょう。
Kernel 初期化の流れの中で Application::registerConfiguredProviders() がコールされます。そこでは $this->config['app.providers'] つまり、PROJECT_ROOT/config/app.php で定義されている配列のキー providers つまりプロバイダーのリストを引数に ProviderRepository::load() をコールしていました。その処理の中で、アプリケーションコンテナの register() に引数としてプロバイダーを一つずつ渡し、そこでプロバダー自体の register() がコールされます。
$this->config['app.providers'] の中には Illuminate\Foundation\Providers\ConsoleSupportServiceProvider が含まれます。
こちらを見てみましょう。
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider
<?php
namespace Illuminate\Foundation\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\MigrationServiceProvider;
use Illuminate\Support\AggregateServiceProvider;
class ConsoleSupportServiceProvider extends AggregateServiceProvider implements DeferrableProvider
{
/**
* The provider class names.
*
* @var array
*/
protected $providers = [
ArtisanServiceProvider::class,
MigrationServiceProvider::class,
ComposerServiceProvider::class,
];
}
コンストラクタも register() も見当たりません。$providers が定義されているだけです。その中に ArtisanServiceProvider とそれっぽいクラスが記述されています。ConsoleSupportServiceProvider クラスは Illuminate\Support\AggregateServiceProvider クラスを継承しています。こちらを見てみましょう。
Illuminate\Support\AggregateServiceProviderL::register()
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->instances = [];
foreach ($this->providers as $provider) {
$this->instances[] = $this->app->register($provider);
}
}
register() メソッドがありました。ConsoleSupportServiceProvider でオーバーライドした $providers を foreach で回してアプリケーションコンテナの register() でプロバイダーを登録して $this->instances[] に入れいていく処理です。ということは、
ArtisanServiceProvider インスタンスがアプリケーションコンテナにプロバイダー登録され、自身の register() メソッドがコールされるはずです。見てみましょう。
Illuminate\Foundation\Providers\ArtisanServiceProvider::register() | 関連メソッド
protected $commands = [
'CacheClear' => 'command.cache.clear',
'CacheForget' => 'command.cache.forget',
'ClearCompiled' => 'command.clear-compiled',
'ClearResets' => 'command.auth.resets.clear',
'ConfigCache' => 'command.config.cache',
'ConfigClear' => 'command.config.clear',
'DbWipe' => 'command.db.wipe',
'Down' => 'command.down',
'Environment' => 'command.environment',
'EventCache' => 'command.event.cache',
'EventClear' => 'command.event.clear',
'EventList' => 'command.event.list',
'KeyGenerate' => 'command.key.generate',
'Optimize' => 'command.optimize',
'OptimizeClear' => 'command.optimize.clear',
'PackageDiscover' => 'command.package.discover',
'QueueFailed' => 'command.queue.failed',
'QueueFlush' => 'command.queue.flush',
'QueueForget' => 'command.queue.forget',
'QueueListen' => 'command.queue.listen',
'QueueRestart' => 'command.queue.restart',
'QueueRetry' => 'command.queue.retry',
'QueueWork' => 'command.queue.work',
'RouteCache' => 'command.route.cache',
'RouteClear' => 'command.route.clear',
'RouteList' => 'command.route.list',
'Seed' => 'command.seed',
'ScheduleFinish' => ScheduleFinishCommand::class,
'ScheduleRun' => ScheduleRunCommand::class,
'StorageLink' => 'command.storage.link',
'Up' => 'command.up',
'ViewCache' => 'command.view.cache',
'ViewClear' => 'command.view.clear',
];
/**
* The commands to be registered.
*
* @var array
*/
protected $devCommands = [
'CacheTable' => 'command.cache.table',
'ChannelMake' => 'command.channel.make',
'ComponentMake' => 'command.component.make',
'ConsoleMake' => 'command.console.make',
'ControllerMake' => 'command.controller.make',
'EventGenerate' => 'command.event.generate',
'EventMake' => 'command.event.make',
'ExceptionMake' => 'command.exception.make',
'FactoryMake' => 'command.factory.make',
'JobMake' => 'command.job.make',
'ListenerMake' => 'command.listener.make',
'MailMake' => 'command.mail.make',
'MiddlewareMake' => 'command.middleware.make',
'ModelMake' => 'command.model.make',
'NotificationMake' => 'command.notification.make',
'NotificationTable' => 'command.notification.table',
'ObserverMake' => 'command.observer.make',
'PolicyMake' => 'command.policy.make',
'ProviderMake' => 'command.provider.make',
'QueueFailedTable' => 'command.queue.failed-table',
'QueueTable' => 'command.queue.table',
'RequestMake' => 'command.request.make',
'ResourceMake' => 'command.resource.make',
'RuleMake' => 'command.rule.make',
'SeederMake' => 'command.seeder.make',
'SessionTable' => 'command.session.table',
'Serve' => 'command.serve',
'StubPublish' => 'command.stub.publish',
'TestMake' => 'command.test.make',
'VendorPublish' => 'command.vendor.publish',
];
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerCommands(array_merge(
$this->commands, $this->devCommands
));
}
/**
* Register the given commands.
*
* @param array $commands
* @return void
*/
protected function registerCommands(array $commands)
{
foreach (array_keys($commands) as $command) {
call_user_func_array([$this, "register{$command}Command"], []);
}
$this->commands(array_values($commands));
}
Illuminate\Support\ServiceProvider::
/**
* Register the package's custom Artisan commands.
*
* @param array|mixed $commands
* @return void
*/
public function commands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();
Artisan::starting(function ($artisan) use ($commands) {
$artisan->resolveCommands($commands);
});
}
$commands と $devCommands に連想配列が沢山代入されています。
register() メソッドは registerCommands() に $commands と $devCommands を array_merge したものを渡してコールしています。
registerCommands() メソッドは受け取った配列を array_keys でキーのみ配列で取り出し、それを foreach で回し、call_user_func_array でコールバック関数を実行します。実行するコールバックは自身に定義してある関数で、関数名は register{$command}Command で生成されたものです。引数は空の配列となっています。
このクラスにはクロージャーをアプリケーションコンテナにシングルトンで結合するメソッドが沢山定義されてます。
以下は例です。
/**
* Register the command.
*
* @return void
*/
protected function registerUpCommand()
{
$this->app->singleton('command.up', function () {
return new UpCommand;
});
}
アプリケーションコンテナに command.up という名前で UpCommand インスタンスを生成して返すクロージャーをシングルトン結合しています。$commands と $devCommands に代入されている沢山のコマンドがシングルトン結合されます。次に
commands() メソッドを $commands を array_values で値のみの配列にしたものを引数にコールします。Artisan::starting() をクロージャーを引数に渡してコールしています。Artisan は Illuminate\Console\Application のことです。見てみましょう。
Illuminate\Console\Application::starting()
/**
* Register a console "starting" bootstrapper.
*
* @param \Closure $callback
* @return void
*/
public static function starting(Closure $callback)
{
static::$bootstrappers[] = $callback;
}
Illuminate\Console\Application::starting()
戻り値はありません。
static::$bootstrappers[] 配列に受け取ったクロージャーを登録しています。
先程疑問だった、static::$bootstrappers に格納されている中身の正体がわかりました。
static::$bootstrappers[] に登録されたクロージャー全てに $this を渡したものをアプリケーションコンテナにシングルトン結合するという手順になります。
せっかくここまで読んだので、Artisan::starting() メソッドに引数として渡されているクロージャーも読んでみましょう。
Artisan::starting(function ($artisan) use ($commands) {
$artisan->resolveCommands($commands);
});
このクロージャーをコールする Artisan::bootstrap() では引数として $this を渡しています。つまり、$this->resolveCommands(コマンド名) が実行されるクロージャーです。 resolveCommands() を見てみましょう。
Illuminate\Console\Application::resolveCommands() | 関連メソッド
/**
* Resolve an array of commands through the application.
*
* @param array|mixed $commands
* @return $this
*/
public function resolveCommands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();
foreach ($commands as $command) {
$this->resolve($command);
}
return $this;
}
/**
* Add a command, resolving through the application.
*
* @param string $command
* @return \Symfony\Component\Console\Command\Command
*/
public function resolve($command)
{
return $this->add($this->laravel->make($command));
}
/**
* Add a command to the console.
*
* @param \Symfony\Component\Console\Command\Command $command
* @return \Symfony\Component\Console\Command\Command
*/
public function add(SymfonyCommand $command)
{
if ($command instanceof Command) {
$command->setLaravel($this->laravel);
}
return $this->addToParent($command);
}
/**
* Set the Laravel application instance.
*
* @param \Illuminate\Contracts\Container\Container $laravel
* @return void
*/
public function setLaravel($laravel)
{
$this->laravel = $laravel;
}
/**
* Add the command to the parent instance.
*
* @param \Symfony\Component\Console\Command\Command $command
* @return \Symfony\Component\Console\Command\Command
*/
protected function addToParent(SymfonyCommand $command)
{
return parent::add($command);
}
Illuminate\Console\Application::resolveCommands()
戻り値は
$this です。
受け取ったコマンドを foreach で回し、resolve() メソッドを引数にコマンド名を渡してコールし、自身を返しています。
resolve() はコマンド名を受け取りそれをアプリケーションコンテナで make() した戻り値を add() しています。
add() メソッドでは、引数として渡されたコマンドが Command インスタンス か検証し、そうであった場合は setLaravel() メソッドにアプリケションコンテナを引数に渡しコールします。
そうでない場合は addToParent() メソッドを通し、スーパークラスの add() メソッドをコールします。
setLaravel() メソッドはコマンドの変数 $laravel にアプリケーションコンテナを代入します。
スーパークラスの add() メソッドとは Symfony\Component\Console\Application::add() の事です。
Symfony\Component\Console\Application::add() が引数として受け取るのは Symfony\Component\Console\Command\Command インスタンスで、これは Illuminate\Console\Command のスーパークラスです。つまり Illuminate\Console\Command は Symfony\Component\Console\Command\Command をLaravel用に拡張したコマンドクラスなのでしょう。
Symfony\Component\Console\Application を読みたいところですが、少しボリュームがあるので、まずは本筋に戻ります。
Illuminate\Foundation\Console\Kernel::handle() から $this->getArtisan() がコールされその処理の途中でした。
Illuminate\Foundation\Console\Kernel:: getArtisan()
/**
* Get the Artisan application instance.
*
* @return \Illuminate\Console\Application
*/
protected function getArtisan()
{
if (is_null($this->artisan)) {
return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version()))
->resolveCommands($this->commands);
}
return $this->artisan;
}
生成した Artisan インスタンスの resolveCommands に $this->commands を引数に渡しコールします。おそらく $this->commands は空の配列なのでただ戻り値に自身が返ってくるだけでしょう。
そのまま生成された Artisan インスタンスが戻されます。
Illuminate\Foundation\Console\Kernel::handle() に戻ります。
Illuminate\Foundation\Console\Kernel::handle()
public function handle($input, $output = null)
{
try {
$this->bootstrap();
return $this->getArtisan()->run($input, $output);
} catch (Throwable $e) {
$this->reportException($e);
$this->renderException($output, $e);
return 1;
}
$this->getArtisan() の戻り値に run() しています。引数に入力と出力を渡しています。次はこちらを見ていきましょう。