INDEX
Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!
- その1「マイグレーションファイルを見てみよう」
- その2「$app['db']って何者?」
- その3「Repositoryクラス」
- その4「Larabelアプリケーションの初期化の流れ」
- その5「リポジトリの読込」
- その6「データベース接続」
- その7「スキーマビルダー」
- その8「migrate コマンドの実行」
- その9「Kernel::handle()」
Kernel::handle()
前回はKernel::handle()
がコールされた際に渡される引数
Symfony\Component\Console\Input\ArgvInput
Symfony\Component\Console\Output\ConsoleOutput
を軽く見てみました。
では、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
です。見てみましょう。
/**
* 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);
}
}
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
にコンストラクタはありません。保持ファンクションの一覧を見てみましょう。
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
クラスは非常に小さな定義のクラスです。
<?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
が含まれます。
こちらを見てみましょう。
<?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
クラスを継承しています。こちらを見てみましょう。
/**
* 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()
メソッドがコールされるはずです。
見てみましょう。
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));
}
/**
* 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
のことです。見てみましょう。
/**
* 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()
を見てみましょう。
/**
* 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()
がコールされその処理の途中でした。
/**
* 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()
に戻ります。
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()
しています。引数に入力と出力を渡しています。
次はこちらを見ていきましょう。
次回
今回はartisan
インスタンスの生成の流れを読んでみました。次回はConsole\Application::run()
を追ってみたいと思います!
続く☆