Laravelを知らない中級（中年）プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない！その８「migrate コマンドの実行」

### INDEX
Laravelを知らない中級（中年）プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない！

- [その１「マイグレーションファイルを見てみよう」](https://qiita.com/xioncc/items/1dcec2fbe3485b7e769a)
- [その２「$app['db']って何者？」](https://qiita.com/xioncc/items/cd4a46e5aca16b13174d)
- [その３「Repositoryクラス」](https://qiita.com/xioncc/items/1ce2132594c4596a73ae)
- [その４「Larabelアプリケーションの初期化の流れ」](https://qiita.com/xioncc/items/a17d7f905d4a5abf8103)
- [その５「リポジトリの読込」](https://qiita.com/xioncc/items/dc7df63c2487b02052f3)
- [その６「データベース接続」](https://qiita.com/xioncc/items/3cf9336eecd5f6905831)
- [その７「スキーマビルダー」](https://qiita.com/xioncc/items/04c95ce9ff6d0b7c9d44)
- [その８「migrate コマンドの実行」](https://qiita.com/xioncc/items/4a5166a0963991e33eb5)
- [その９「Kernel::handle()」](https://qiita.com/xioncc/items/07c1f0ba9b40bd230608)


## php artisan migrate
いよいよ``migrate``コマンドが叩かれる部分を読もうと思います。
マイグレーションを実行する際、コマンドラインから以下のコマンドを叩きます。

```bash
$ php artisan migrate
```

コマンドが叩かれると、``PROJECT_ROOT/artisan``が実行されます。もう一度内容を見てみましょう。

```php: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``サービスがコンテナに登録されます。ここまで長いこと読んできたのでそこまではもう問題なく理解できますね。いよいよメインディッシュです。

```php
$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``のようです。

```php:Symfony\Component\Console\Input\ArgvInput&#x3a;&#x3a;__construct()
    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);
            }
        }
    }
```

```php:Symfony\Component\Console\Input\Input&#x3a;&#x3a;__construct()
    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``クラスは、コマンドオプションの記述ルールを定義するクラスのようです。コードドキュメントに以下のような記述があります。

```php
 * 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``のようです。

```php:ConsoleOutput&#x3a;&#x3a;__construct()|関連メソッド
    /**
     * @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``の前にバックスラッシュがついています。明示しないとかぶるファンクションのようなものがあるのでしょうか。なぜ必要なのかちょっと思い当たりません。

```php:StreamOutput&#x3a;&#x3a;__construct()
    /**
     * @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に対応している場合装飾します。

非常に面白そうですね。機会があったら掘り下げたいところです。

```php:Output&#x3a;&#x3a;__construct()
    /**
     * @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``の場合はデフォルトのフォーマッタが使われます。
戻り値はありません。

受け取った引数から、冗長レベルとフォーマッタを設定しフォーマッタに装飾ステータスをセットします。

```php:OutputInterface&#x3a;&#x3a;const
    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()``メソッドから追いかけていきたいと思います。

続く☆