Symfony2ソースコードリーディング (1) app/consoleを読み解く

Symfony2の使い方を勉強するためにソースコードを読んでみることにしました。

対象はSymfony 2.6です。ソースコードはこちら。
https://github.com/symfony/symfony/tree/2.6

# なぜ読もうと思ったのか

フレームワークを勉強する場合はソースを見るほうが早いというのが、私がEthnaで学んだ教訓だからです。
マニュアルで抽象的な説明を読んでもモヤモヤすることが多いのと、フレームワークはアプリケーションと同じ言語で書かれているので読んだほうが絶対お得なのです。

# 実行環境

OSX (Yosetemite)にPHP5.6をtar.gzからコンパイルしてインストールしました。
(まあインストール方法はなんでもよいでしょう)

PHP StormとかのIDEでコードを開くのが良さそうですが、とりあえずEmacsで(老害)

# 準備

[Symfony Installer](http://symfony.com/doc/current/book/installation.html) を導入して、`symfony new blog`を実行してプロジェクトのひな形を作成しました。

本当は仕組みを学ぶためにはこのようなInstallerを使わずに手動でやるべきですが、そこは後回し。

作成後のディレクトリツリー

```
$ cd blog
$ ls -l
total 68
-rw-rw-rw-  1 DQNEO staff    63  5 15 22:20 README.md
drwxr-xr-x 15 DQNEO staff   510  5 11 11:22 app/
drwxr-xr-x  5 DQNEO staff   170  5 11 11:22 bin/
-rw-r--r--  1 DQNEO staff  2592  5 15 22:20 composer.json
-rw-r--r--  1 DQNEO staff 60129  5 11 11:20 composer.lock
drwxr-xr-x  5 DQNEO staff   170  5 11 11:22 src/
drwxr-xr-x 15 DQNEO staff   510  5 11 11:22 vendor/
drwxr-xr-x 10 DQNEO staff   340  5 11 11:22 web/
```

各ディレクトリの役割は、

|ディレクトリ|役割|
|---|---|
|web|外部からアクセスしてもらうためのDocumentRoot|
|vendor|composerで入れた各種パッケージ (Symfonyのフレームワーク本体もここにある)|
|bin|よくわからんので無視|
|src|自分のプロジェクトバンドル (バンドルとは、モジュールのこと)|
|app|プロジェクト固有の設定ファイル的なもの|

srcとappの違いがよくわからなかったのですが、@77web さん @hidenorigoto さん が教えてくださいました。

<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/DQNEO">@DQNEO</a> その構造、私的にはイチオシです。もともとモジュール思想が強く、src配下には例えていうならCPANに上げるようなパッケージ的にバンドルを作るんです。それを特定アプリケーション向けに組み合わせるコンフィギュレーション等がapp配下に入ります。</p>&mdash; Hidenori Goto (@hidenorigoto) <a href="https://twitter.com/hidenorigoto/status/599180849532116993">May 15, 2015</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

ありがとうございます！

# まずはサーバを起動してみる
`app/console server:run`すると、PHPビルトインサーバが起動してSymfonyのWelcome画面が表示されました。

ここで疑問がふつふつと湧いてきます。

* `app/console` はナニ？
* `server:run` の左辺と右辺の関係は？実体は？
* どうやってビルトインサーバを起動してるの？

これらの疑問を放置したまま先に進むことができない性格なので、おもむろにソースコードを読みはじめました。

ちなみにフレームワークのソースコードは、作成したプロジェクトディレクトリの下の`vendor/symfony/symfony`にあります。

デバグ方法は、当該ファイルを開いて`var_dump($var);exit;`するという、あたたかみのある原始的printデバグです。笑

# `app/console` はナニ？

まずここが出発地点です。
これはただのPHPスクリプトです。

```php:app/console
#!/usr/bin/env php
<?php

// if you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
//umask(0000);

set_time_limit(0);

require_once __DIR__.'/bootstrap.php.cache';
require_once __DIR__.'/AppKernel.php';

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;

$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';

if ($debug) {
    Debug::enable();
}

$kernel = new AppKernel($env, $debug);
$application = new Application($kernel);
$application->run($input);
```

まず冒頭の`bootstrap.php.cache`を見てみましょう。
・・んがっ、開いた瞬間に「むぐぐっ」という感じだったのでそっ閉じしました。どうもありがとうございました笑。後回し後回し。

次、`AppKernel.php`。これの実体は [Symfony\Component\HttpKernel\Kernel](https://github.com/symfony/symfony/blob/2.6/src/Symfony/Component/HttpKernel/Kernel.php)
を継承した子クラスです。
フレームワークの核となるクラスのようです。これも後回し。

このカーネルクラスのインスタンスを [Symfony\Bundle\FrameworkBundle\Console\Application](https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/FrameworkBundle/Console/Application.php) に食わせてrunしています。

「app/consoleの実体はSymfony\Bundle\FrameworkBundle\Console\Application::run()である」ということは言えそうです。

# Symfony\Bundle\FrameworkBundle\Console\Application

さて`Symfony\Bundle\FrameworkBundle\Console\Application`を開いてみたところ、ここにrunメソッドはありませんでした。
親クラスの [Symfony\Component\Console\Application](https://github.com/symfony/symfony/blob/2.6/src/Symfony/Component/Console/Application.php#L113) にありました。

run()の実体は

```php:Symfony/Component/Console/Application.php
 $exitCode = $this->doRun($input, $output);
```

なので、結局また子クラス(Symfony\Bundle\FrameworkBundle\Console\Application)のdoRun()が呼ばれています。
子クラスのdoRun()では、前処理的なゴニョゴニョが行われた後で最後に

```php:Symfony/Bundle/FrameworkBundle/Console/Application.php
return parent::doRun($input, $output);
```

となっており、また親クラスのdoRun()に制御が戻ります。親と子を行ったり来たりして若干追いづらいですね。

親クラスのdoRun()を見てみましょう。

```php:Symfony/Component/Console/Application.php
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--version', '-V'))) {
            $output->writeln($this->getLongVersion());
            return 0;
        }
        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(array('--help', '-h'))) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(array('command' => 'help'));
            } else {
                $this->wantHelps = true;
            }
        }
        if (!$name) {
            $name = $this->defaultCommand;
            $input = new ArrayInput(array('command' => $this->defaultCommand));
        }
        // the command name MUST be the first element of the input
        $command = $this->find($name);
        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;
        return $exitCode;
    }

```

ここが`app/console`の受付入り口のような感じです。
入り口で`--help`と`--version`オプションを取り扱って、それ以外の場合はサブコマンドを実行する、という感じです。

次回はここにvar_dump()デバグをしかけて挙動を詳しく見て行きたいと思います。
つづく