INDEX
Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!
- その1「マイグレーションファイルを見てみよう」
- その2「$app['db']って何者?」
- その3「Repositoryクラス」
- その4「Larabelアプリケーションの初期化の流れ」
- その5「リポジトリの読込」
- その6「データベース接続」
- その7「スキーマビルダー」
- その8「migrate コマンドの実行」
- その9「Kernel::handle()」
php artisan migrate
いよいよmigrate
コマンドが叩かれる部分を読もうと思います。
マイグレーションを実行する際、コマンドラインから以下のコマンドを叩きます。
$ php artisan migrate
コマンドが叩かれると、PROJECT_ROOT/artisan
が実行されます。もう一度内容を見てみましょう。
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);
実行開始時間がセットされ、composer
のオートロードが読み込まれ、PROJECT_ROOT/bootstrap/app.php
が読み込まれ、Kernel
サービスがコンテナに登録されます。ここまで長いこと読んできたのでそこまではもう問題なく理解できますね。いよいよメインディッシュです。
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
Kernel::handle()
メソッドがコールされます。コマンドの実行です。
引数にSymfony\Component\Console\Input\ArgvInput
とSymfony\Component\Console\Output\ConsoleOutput
が渡されています。
見てみましょう。
Symfony\Component\Console\Input\ArgvInput
autoload_classmap
によると、実体は/symfony/console/Input/ArgvInput.php
のようです。
public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
}
// strip the application name
array_shift($argv);
$this->tokens = $argv;
parent::__construct($definition);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
public function __construct(InputDefinition $definition = null)
{
if (null === $definition) {
$this->definition = new InputDefinition();
} else {
$this->bind($definition);
$this->validate();
}
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition)
{
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
}
/**
* Processes command line arguments.
*/
abstract protected function parse();
/**
* {@inheritdoc}
*/
public function validate()
{
$definition = $this->definition;
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (\count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
}
}
ArgvInput::__construct()
第一引数は配列型でコマンドラインの引数です。
第二引数はInputDefinition
インスタンスです。
戻り値はありません。
ArgvInput
クラスはコマンドラインから受け取る引数を解析する仕組みを持ちます。
第一引数があればその配列を、なければ$_SERVER['argv']
を$tokens
に代入します。
コマンドラインの引数を代入した$token
配列の0番目、つまりコマンド名を削除します。今回の場合は 0番目に「artisan
」 というストリングが入っていますのでそれを削除します。
その後、第二引数にInputDefinition
インスタンスが渡されていればそれを、なければnull
を引数にし、継承したInput
クラスのコンストラクタをコールします。
Input::__construct()
は引数としてInputDefinition
インスタンスが渡されていた場合、それをバインドし、validate()
メソッドをコールします。
渡されていなければInputDefinition
インスタンスを生成し、$definition
に代入します。
InputDefinition
クラスは、コマンドオプションの記述ルールを定義するクラスのようです。コードドキュメントに以下のような記述があります。
* A InputDefinition represents a set of valid command line arguments and options.
*
* Usage:
*
* $definition = new InputDefinition([
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ]);
引数にInputOption
型を格納した配列を受け取り、引数とオプションを管理するもののようです。
これ以上はSymfony
の話になりますのでディテールリーディングは別の機会にしますが、bind()
で引数、オプション、オプション定義をセットし、オーバーライドしたArgvInput::parse()
で解析し、validate()
で処理をする流れと読み解けます。
Symfony\Component\Console\Output\ConsoleOutput
ConsoleOutput
クラスの方も見てみましょう。
autoload_classmap
によると、実体は/symfony/console/Output/ConsoleOutput.php
のようです。
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
if (null === $decorated) {
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
}
}
/**
* @return resource
*/
private function openOutputStream()
{
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
{
return false === $this->isRunningOS400();
}
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*/
private function isRunningOS400(): bool
{
$checks = [
\function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
PHP_OS,
];
return false !== stripos(implode(';', $checks), 'OS400');
}
ConsoleOutput::__construct()
第一引数は整数型で冗長レベル指定定数です。
第二引数はブーリアン型若しくはnull
でメッセージを装飾するかを決めるパラメータです。
第三引数はOutputFormatterInterface
インスタンスで出力用のフォーマッタです。null
の場合はデフォルトのフォーマッタが使われます。
戻り値はありません。
ConsoleOutput
クラスはコマンドラインインターフェースでの出力を担うクラスです。
第一引数は冗長性レベル定数の指定とあります。ConsoleOutput
クラスは多段階継承をしていて、冗長性レベルの定数はその基底クラスで定義されています。
処理の最初にいきなりスーパークラスのコンストラクタをコールしています。その第一引数にopenOutputStream()
メソッドをコールしています。これは実行環境を確認しIBM
のiSeries (OS400)
だった場合、文字エンコードをASCII
からEBCDIC
に正しく変換しないため、出力ストリームをphp://output
でオープンし、そうでない場合はphp://stdout
で出力ストリームをオープンできるか試し、出来ない場合はphp://output
でオープンしたものを返すメソッドです。とても面白そうですが、脱線がすぎるので今回は深追いしません。
他はほぼ受け取った引数をそのままスーパークラスに渡しています。
StreamOutput::__construct()
を見てみましょう。
渡された第一引数がストリームかどうか検証しています。ストリームでない場合は「The StreamOutput class needs a stream as its first argument.」というメッセージを添えて例外InvalidArgumentException
をスローします。function_exists
やis_resource
の前にバックスラッシュがついています。明示しないとかぶるファンクションのようなものがあるのでしょうか。なぜ必要なのかちょっと思い当たりません。
/**
* @param resource $stream A stream resource
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*
* @throws InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
}
$this->stream = $stream;
if (null === $decorated) {
$decorated = $this->hasColorSupport();
}
parent::__construct($verbosity, $decorated, $formatter);
}
/**
* Returns true if the stream supports colorization.
*
* Colorization is disabled if not supported by the stream:
*
* This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
* terminals via named pipes, so we can only check the environment.
*
* Reference: Composer\XdebugHandler\Process::supportsColor
* https://github.com/composer/xdebug-handler
*
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}
if (\DIRECTORY_SEPARATOR === '\') {
return (\function_exists('sapi_windows_vt100_support')
&& @sapi_windows_vt100_support($this->stream))
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
return stream_isatty($this->stream);
}
StreamOutput::__construct()
第一引数はリソース型でストリームリソースです。
第二引数は整数型で冗長レベル定数です。
第三引数はブーリアン型若しくはnull
でメッセージを装飾するかを決めるパラメータです。
第四引数はOutputFormatterInterface
インスタンスで出力用のフォーマッタです。null
の場合はデフォルトのフォーマッタが使われます。
戻り値はありません。
ConsoleOutput::__construct()
からそのままの引数に加えてストリームリソースを渡されます。
受け取った第一引数がストリームリソースでない場合「The StreamOutput class needs a stream as its first argument.」とメッセージを添えて例外InvalidArgumentException
をスローします。
受け取ったストリームリソースを$this->stream
に代入します。
第三引数はメッセージ装飾をするか否かのステータスです。これにnull
が渡された場合、hasColorSupport()
メソッドがコールされ、自動で判定されます。
判定ロジックは以下です。
サーバ変数若しくはgetenv()
の「NO_COLOR
」がtrue
にセットされていた場合、装飾しません。
getenv()
の「TERM_PROGRAM
」がHyper
にセットされていた場合、装飾します。
ディレクトリの区切り文字が「\\
」で且つ、Windows
コンソールの出力バッファに関連付けられたストリームのVT100サポートしていて、getenv()
で確認しそれが利用できる設定だった場合、装飾します。
上記以外の場合でストリームがTTYに対応している場合装飾します。
非常に面白そうですね。機会があったら掘り下げたいところです。
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool $decorated Whether to decorate messages
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
ConsoleOutput::__construct()
第一引数は整数型で冗長レベル指定定数です。
第二引数はブーリアン型若しくはnull
でメッセージを装飾するかを決めるパラメータです。
第三引数はOutputFormatterInterface
インスタンスで出力用のフォーマッタです。null
の場合はデフォルトのフォーマッタが使われます。
戻り値はありません。
受け取った引数から、冗長レベルとフォーマッタを設定しフォーマッタに装飾ステータスをセットします。
const VERBOSITY_QUIET = 16;
const VERBOSITY_NORMAL = 32;
const VERBOSITY_VERBOSE = 64;
const VERBOSITY_VERY_VERBOSE = 128;
const VERBOSITY_DEBUG = 256;
const OUTPUT_NORMAL = 1;
const OUTPUT_RAW = 2;
const OUTPUT_PLAIN = 4;
冗長レベル定数は上記のように定義されています。
Symfony\Component\Console\Input\ArgvInput
とSymfony\Component\Console\Output\ConsoleOutput
はコマンドラインの入出力を担うクラスということがわかりました。
今回は Laravel というより Symfony のお話でしたね。もしかしたら今後 Symfony のコードを色々と読む必要があるのかもしれません。
次回
次回はKernel::handle()
メソッドから追いかけていきたいと思います。
続く☆