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 さん が教えてくださいました。
@DQNEO その構造、私的にはイチオシです。もともとモジュール思想が強く、src配下には例えていうならCPANに上げるようなパッケージ的にバンドルを作るんです。それを特定アプリケーション向けに組み合わせるコンフィギュレーション等がapp配下に入ります。
— Hidenori Goto (@hidenorigoto) May 15, 2015
ありがとうございます!
まずはサーバを起動してみる
app/console server:run
すると、PHPビルトインサーバが起動してSymfonyのWelcome画面が表示されました。
ここで疑問がふつふつと湧いてきます。
-
app/console
はナニ? -
server:run
の左辺と右辺の関係は?実体は? - どうやってビルトインサーバを起動してるの?
これらの疑問を放置したまま先に進むことができない性格なので、おもむろにソースコードを読みはじめました。
ちなみにフレームワークのソースコードは、作成したプロジェクトディレクトリの下のvendor/symfony/symfony
にあります。
デバグ方法は、当該ファイルを開いてvar_dump($var);exit;
するという、あたたかみのある原始的printデバグです。笑
app/console
はナニ?
まずここが出発地点です。
これはただのPHPスクリプトです。
#!/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()の実体は
$exitCode = $this->doRun($input, $output);
なので、結局また子クラス(Symfony\Bundle\FrameworkBundle\Console\Application)のdoRun()が呼ばれています。
子クラスのdoRun()では、前処理的なゴニョゴニョが行われた後で最後に
return parent::doRun($input, $output);
となっており、また親クラスのdoRun()に制御が戻ります。親と子を行ったり来たりして若干追いづらいですね。
親クラスのdoRun()を見てみましょう。
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()デバグをしかけて挙動を詳しく見て行きたいと思います。
つづく