CakePHP 3 の ビルトインサーバ を読み解く


CakePHP 3 の ビルトインサーバ の実態がなんなのか気になったので 調べました。 php bin/cake.php server で動くサーバです。

環境

  • PHP 5.5.9
  • cakephp/cakephp 3.0.9
  • Ubuntu 14.04.2 LTS

コマンドは php bin/cake.php server なので、 cake.php から順にコードを読み進めます。

cake.php

今回だけでなく、コマンドを実行する時に使う最初のファイルです。 ディレクトリ bin の中に cakecake.batcake.php がありますが、 cakecake.batcake.phpを実行しているだけです。 cake はシェルスクリプト 、 cake.bat は Windows 用 bat ファイル です。

cake.php の中では config/bootstrap.php を読み込んで実行し、 CakePHP 3 のコアファイルを読み込んでいます。 そして ShellDispatcher で実際のコマンド内容を処理します。 注目すべきは最後の行です。

CakeConsoleShellDispatcher::run($argv) の返り値がコマンドの終了ステータスになります。

CakeConsoleShellDispatcher::run($argv) の中を見ていきます。

run

パラメータが new ShellDispatcher($argv) で渡され、 dispatch() で処理を実行します。

ShellDispatcher では コンストラクタで色々処理をやっていますが、 重要なのは $argv をインスタンス変数に格納していることです。

dispatch

dispatch という名前だけあって、 振り分けに徹したメソッドとなっています。

dispatch は内部で _dispatch を呼んでいます。 実質的な処理は _dispatch の中で完結し、 dispatch は結果(終了ステータス)のみ返します。

_dispatch

パラメータに応じてシェルを探して実行します。

findShellrumCommand がポイントですね。

_dispatch()shiftArgs のところは、 一番最初のパラメータを取り出しています。 今回題材にしている サーバ起動の場合は 'server' になります。 そして、 パラメータがない または help--help-h のいずれかが最初のパラメータになっていた場合は help を表示します。 また、 パラメータがない場合には false を返し、 最終的に 終了ステータス 1 (失敗) が返ります。

findShell

実際の処理が書かれたシェルを呼び出すところです。 cake.phpphp bin/cake.php bakephp bin/cake.php migrations のように呼び出され、 $shell の中には 'bake''migratoins' が入ります。 今回は 'server' が入ります。

$shell には 'server' が入っています。 _shellExists でクラス名を取得して、 _createShell でインスタンスを作成します。

_shellExists

コードを見ると、 App::className でクラス名を取得していることがわかります。

クラスが存在すればクラス名を返すようになっています。

CakeCoreApp の メソッド classNameを 見てみます。

App

className メソッド に渡されるパラメータは 'server''Shell''Shell' でした。 pluginSplitcakephp/src/Core/functions.php で定義された関数で、 ここでは [null, 'server'] が返ります。

Configure::read では config.php に書かれた namespace の値を取得しています。 特に設定していなければ 'App' になります。

また、 コード内の変数 $fullname'ShellserverShell' になります。

以上を踏まえ、 _classExistsInBase で何が返ってくるのかを考えます。

_classExistsInBase

_classExistsInBase はクラスが定義されているか否かをチェックするメソッドになっており、 1番目の引数がクラス名、 2番目の引数が名前空間になっています。

内部で PHP の関数 class_exists を呼び出しています。 クラスが定義されていれば true 、 定義されていなければ false になります。

className の中では 2回 _classExistsInBase を呼び出していました。 1回目は _classExistsInBase('ShellserverShell', 'App') 、 2回目は _classExistsInBase('ShellserverShell', 'Cake') です。

2回目に呼び出されたときには 惜しくも class_exists('CakeShellserverShell') が実行されて false が返ります。 OS によってここで返ってくる値が変わりますが、心配することはありません。 この後 'server''s' が大文字になって再実行されます。 (クラスのロードは bootstrap.php を通じて行われていますが、 ここでは深追いしません。)

_classExistsInBasefalse を返すので、 _shellExistsfalse を返し、 findShell の 最初の if 節 が実行されます。

_handleAlias($shell) が実行されているのでその中を見てみましょう。

_handleAlias

_handleAlias では 登録されているエイリアスと比較し、 登録されていなければ スネークケースをキャメルケースに変換して返します。 キャメルケースに変換する場合は、 'abc_def.ghi' という形の文字列が Abc_Def.Ghi に変換されます。

static::alias('server') の返り値は false になります。 aliasに関するコードは別の機会に読み解くことにします。

CakeUtilityInflector::camelize' も概要をいえば (キャッシュも使って)スネークケースをキャメルケースにするメソッドです。 ここでは深追いしません。

_handleAlias を経て、 $shell の値が 'Server' になります。 そして、 晴れて _shellExists('Server') が実行されます。 さっきは _shellExists('server') が実行されて、 'server' の先頭が小文字だったために 結果が false になっていました。 今回は _shellExistsApp::className('Server') すなわち CakeShellServerShell を返します。 そして _createShell('CakeShellServerShell', 'server') が実行されます。

最初から php bin/cake.php Server とサーバを起動してもよかったことがわかります。

_createShell

ここでは指定されたクラスのインスタンスを作ります。 一番重要なのは new $className() のところですね。

pluginSplit は先ほども出てきました。 ここでは $pluginnull が入ります。 そして $instance->plugin というのは CakeShellServerShell が継承する CakeConsoleShell で定義されたインスタンス変数で、 初期値は null です。

findShell の返り値として $Shell にインスタンスが代入されました。 dispatch() はあと2行で終わります。

$Shell->initialize() では ShellServerinitialize メソッド が実行されます。

initialize

ここではすべて初期値が設定されます。

WWW_ROOTbootstrap.php の中で読み込まれる path.php で定義されているもので、 webroot の絶対パスが設定されています。

そして次は runCommand が実行されます。 runCommandShell で定義されています。

runCommand

最初にオプションの処理をして、オプションに応じてメソッドが実行されます。 今回は return call_user_func_array([$this, 'main'], $this->args); の行で ServerShellmain が実行されてサーバが起動します。 getOptionParser()startup()call_user_func_array([$this, 'main'], $this->args)がポイントです。

$this->getOptionParser() に注意してください。 ここで実行されるメソッドは、 Shell のものではなく、 Shell を継承した ServerShell のコードです。

まずはオプションを処理する getOptionParser を見てみます。

getOptionParser

このメソッドを見ると、サーバ起動時にどんなパラメータが設定できるのか推測できます。 メソッドの返り値は、 ConsoleOptionParser のインスタンスです。

設定できるパラメータ
H, host ホスト名 ・ IPアドレス
p, port ポート番号
d, document_root ドキュメントルート

設定できるのは、 ServerShellinitialize で初期値に設定されたものです。

ここでは深入りしませんが、 親クラスの Shell に記述された getOptionParser が実行されています。 そして descriptionaddOption によって、 ServerShell 特有の説明とオプションが設定されています。

parse

このメソッドでは、コマンドで渡されたオプションに基づいてインスタンス変数に値を格納します。 内部で いくつかメソッドを呼び出して、 $params という変数(連想配列)にパラメータを格納します。

返り値は [$params, $arg]$arg には オプション ではない値が格納されます。 今回は空の配列です。

parse でオプション設定が終わったら、 次は startup で起動の準備が行われます。

startup

ここで設定したパラメータが適用されていきます。

parse で設定された ホスト、ポート、ドキュメントルートを設定しています。 そして 親クラスの startup() を呼び出しています。 親クラスの startup()welcome message を表示します。

preg_match のところは Windows のファイルシステムで c:docroot などが指定された場合に cdocroot に変更するものと思われます。

main

ようやく サーバ起動部分のコードに辿り着きました。 call_user_func_arraymain が呼び出されます。

結局これです。 php -S host:port -t documentroot documentroot/index.php が実行されています。

$port という変数を定義していますが、 定義しなくてもいいような気がします。

PHP のマニュアルを見ると分かりますが、 -S の直後に指定されているのが待ち受けるホスト名とポート、 -t の直後に書かれているのが ドキュメントルート、 その後に書かれているのが ルータスクリプトです。 ルータスクリプトは、あるURLで来たリクエストについてどのような処理をするのかをスクリプトとして書くことができます。 通常指定されるルータスクリプト index.php では、 存在するファイルがリクエストされたら false を返し、 php では処理しないようになっています。