Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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 を導入して、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 さん が教えてくださいました。

ありがとうございます!

まずはサーバを起動してみる

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

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

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

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

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

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

app/console はナニ?

まずここが出発地点です。
これはただの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
を継承した子クラスです。
フレームワークの核となるクラスのようです。これも後回し。

このカーネルクラスのインスタンスを Symfony\Bundle\FrameworkBundle\Console\Application に食わせてrunしています。

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

Symfony\Bundle\FrameworkBundle\Console\Application

さてSymfony\Bundle\FrameworkBundle\Console\Applicationを開いてみたところ、ここにrunメソッドはありませんでした。
親クラスの Symfony\Component\Console\Application にありました。

run()の実体は

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

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

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

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

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

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()デバグをしかけて挙動を詳しく見て行きたいと思います。
つづく

DQNEO
PHP/Go/Perl/C/Docker/Linux/Git/AWS
http://dqn.sakusakutto.jp
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした