Symfony2ソースコードリーディング (1) app/consoleを読み解く の続きです。
app/console
の中身を見ていきます。
app/console
の実体が Symfony\Component\Console\Application であることは前回説明しました。
入り口は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;
}
上から順に見ていきましょう。
まず最初のif文は、引数が--version
または-V
だったらバージョン番号を表示して終了、のように読めます。
デバグ用コードを仕込んで確かめてみましょう。
if (true === $input->hasParameterOption(array('--version', '-V'))) {
$output->writeln($this->getLongVersion());
+ die("( ˘ω˘)" . __METHOD__);
return 0;
}
実行してみます。
$ blog ./app/console --version
Symfony version 2.6.7 - app/dev/debug
( ˘ω˘) Symfony\Component\Console\Application::doRun
$ blog ./app/console -V
Symfony version 2.6.7 - app/dev/debug
( ˘ω˘) Symfony\Component\Console\Application::doRun
確かに、バージョンを表示して終了しました。
次に、
$name = $this->getCommandName($input);
これはなんでしょうか。
getCommandNameメソッドの定義は同じファイル内にあります。
protected function getCommandName(InputInterface $input)
{
return $input->getFirstArgument();
}
一つ目の引数を取得してるようです。つまりサブコマンド名を取得してると思われます。
これを確かめます。
$name = $this->getCommandName($input);
+ var_dump($name);exit;
$ blog ./app/console help
string(4) "help"
$ blog ./app/console server:run --help
string(10) "server:run"
$ blog ./app/console
NULL
ビンゴ!$nameにはサブコマンドが入るようです。
つぎ。
if (true === $input->hasParameterOption(array('--help', '-h'))) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(array('command' => 'help'));
} else {
$this->wantHelps = true;
}
}
これは、--help
または-h
オプションをつけた時の分岐です。
-
app/console server:run --help
の場合はサブコマンドserver:run
のヘルプを表示する -
app/console --help
場合はapp/console help
と解釈しなおしてapp/console
自体のヘルプを表示する
となっています。
(余談ですが、関数型プログラミングではImmutableとか言って再代入を避けるのがよいとされていますが、ここでは躊躇なく$inputに再代入しています。まあPHPだから別に気にすることないのかも)
つぎ。
if (!$name) {
$name = $this->defaultCommand;
$input = new ArrayInput(array('command' => $this->defaultCommand));
}
$nameが未定義の場合にデフォルトコマンドを割り当てています。
デフォルトコマンドとは何か?
defaultCommandがどこで定義されてるのか探してみたらコンストラクタにありました。
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->defaultCommand = 'list';
$this->helperSet = $this->getDefaultHelperSet();
$this->definition = $this->getDefaultInputDefinition();
foreach ($this->getDefaultCommands() as $command) {
$this->add($command);
}
}
list
という値が設定されています。
つまり、
app/console
はapp/console list
と等価になります。
これどこかで見たことのある挙動だぞー?
と思っていたら、Composerがそうでした。
composer
とcomposer list
は同じ挙動でした。最近の流行りなんでしょうか・・・?
つぎ。
$command = $this->find($name);
デバグしてみましょう
$command = $this->find($name);
+ var_dump(get_class($command));exit;
$ blog ./app/console list
string(45) "Symfony\Component\Console\Command\ListCommand"
$ blog ./app/console help
string(45) "Symfony\Component\Console\Command\HelpCommand"
$ blog ./app/console yaml:lint
string(54) "Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand"
$ blog ./app/console server:run
string(55) "Symfony\Bundle\FrameworkBundle\Command\ServerRunCommand"
$ blog ./app/console acme:hello
string(41) "Acme\DemoBundle\Command\HelloWorldCommand"
サブコマンドの名前に応じて、コマンドクラスのインスタンスが返ってきています。
注目すべきは、サブコマンドの名前空間がばらばらな点です。
これはどういう探索ロジックになってるんでしょう?探索先の優先順位とかがあるのかな?(その調査は後回しにします)
サブコマンドの文字列server:run
をオブジェクトSymfony\Bundle\FrameworkBundle\Command\ServerRunCommand
に変換する処理はfind()メソッドにありました。
/**
* Finds a command by name or alias.
*
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @param string $name A command name or a command alias
*
* @return Command A Command instance
*
* @throws \InvalidArgumentException When command name is incorrect or ambiguous
*
* @api
*/
public function find($name)
{
$allCommands = array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
$this->commandsの中身を調べてみると、
array(
'help' => $helpCommand,
'server:run' => $serverRunCommand,
...
)
のようなkey/value構造になっており、valueの部分には各種サブコマンドクラスのインスタンスが保持されています。
サブコマンドが全部で61個(!)もありました。すごい。
あらかじめ61個のサブコマンドを全てnewして配列上に保持した上で、入力されたサブコマンド名にマッチするものを返します。
なんとも富豪的アプローチですw。
まあapp/console
はそんなに頻繁に起動するものでもないからパフォーマンスを気にする必要はないのでしょう。
サブコマンドの実体を見てみる
さてサブコマンドhelp
の実体は、上でのべたように
Symfony\Component\Console\Command\HelpCommand です。
中身はこうなっておりました。
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelpCommand extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this
->setName('help')
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output help in other formats', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
))
->setDescription('Displays help for a command')
->setHelp(<<<EOF
The <info>%command.name%</info> command displays help for a given command:
<info>php %command.full_name% list</info>
You can also output the help in other formats by using the <comment>--format</comment> option:
<info>php %command.full_name% --format=xml list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
)
;
}
ふむふむ。これがhelp
サブコマンドの設定ですね。
実行部分はexecute()メソッドで、
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command) {
$this->command = $this->getApplication()->find($input->getArgument('command_name'));
}
if ($input->getOption('xml')) {
$input->setOption('format', 'xml');
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
));
$this->command = null;
}
とくに実体は何もなく、説明文を表示して終了という感じです。
次回は、サブコマンドserver:run
の実体に迫ります。
つづく