PHPを使って小さなツール、具体的には2~10画面程度の規模のツールを作りたい場合があります。
このような規模感のツールを作る場合、SymfonyやLaravel等のフレームワークを使うと、やりたいことに対してプログラムの規模が少し大げさになってしまいます。一方で複数画面から構成されるので、素のphpファイルをDocumentRootに置くのは将来の拡張性を考えたときと避けたいです。
フレームワークは使いたくなくないがURLに応じたルーティング処理だけ導入したいような場合、nikic/fast-route
パッケージ(FastRoute)を使用すると作成するシステムへルーティング処理だけを導入することができます。nikic/fast-route
パッケージは処理も高速で、PHPマイクロフレームワークであるSlimFrameworkなどでも利用されているため、このような状況にはベストな選択肢です。
本記事では、nikic/fast-routeパッケージを利用した小さなルーティングプログラムを作成してみます。
nikic/fast-routeをインストールする
nikic/fast-routeパッケージはcomposerで提供されているため、下記のコマンドで導入できます。
composer require nikic/fast-route
nikic/fast-routeを利用したルーティングプログラム
まず、プログラムを格納するためのpublicディレクトリを作成します。この作業は必須ではありませんが、composer.jsonやvendorが含まれるフォルダをDocumentRoot以下に置かないための処置です。
> mkdir public
> ls
composer.json
composer.lock
public/
vendor/
publicフォルダを作ったら、この下にindex.phpファイルを作成しルーティング処理を作成していきます。
public/index.phpの作成
<?php
/* public/index.php */
require '../vendor/autoload.php';
まずはファイルの先頭で、Composerのautoload.phpによりオートローダを有効にします。
function route()
{
// ルーティングのルールを指定する
$dispatcher = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/users', 'get_all_users');
$r->addRoute('GET', '/user/{id:\d+}', 'get_user');
});
次に、リクエストURIに応じたルーティング処理行うroute()メソッドを作成します。
ここではsimpleDispatcher利用したルーティングを作成します。/users
にアクセスされたときはget_all_users、'/user/999'などuserの後に数字が指定されたときはget_userのアクションを実行させるよう指定しています。後者はパラメータ付きのルーティングでパラメータ部は{id:\d+}のように{
と}
で囲った上で、カッコの中には、キー + ":" + 値を表す正規表現
の形式で記載します。
// リクエストパラメータを取得する
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
// リクエストURLからクエリストリング(?foo=bar)を除去したうえで、URIデコードする
$pos = strpos($uri, '?');
if ($pos !== false) {
$uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);
次は、クライアントからのリクエストURIの解析処理です。ここは定型なのでこのまま書けばOKです。
// ルーティングに従った処理を行う
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
解析されたURIを元に、dispatch()メソッドでルーティングの適用を行います。
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::FOUND:
// ルーティングに従って処理を実行
$handler = $routeInfo[1];
$vars = $routeInfo[2];
doAction($handler, $vars);
break;
case FastRoute\Dispatcher::NOT_FOUND:
// Not Foundだった時
echo "404 Not Found.";
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
// Method Not Allowedだった時
$allowedMethods = $routeInfo[1];
echo "405 Method Not Allowed. allow only=" . json_encode($allowedMethods);
break;
default
echo "500 Server Error.";
break;
}
}
dispatch()メソッドはルーティング結果を配列で返します。指定されたURIがルーティング可能か否かを配列の0番目で返すため、この結果をもとに分岐します。
FastRoute\Dispatcher::FOUNDが返されたときが正常終了で、ルーティングの結果が配列の1,2番目に返されます。ここではdispatch()の動作を理解するために2つのパラメータを引数にしてdoAction()メソッドを呼び出すことにします。
route();
関数定義が終わったら、先ほど作成したroute()メソッドをコールします。
これでroute()メソッドの実装は終わりです。
次はdoAction()の中身を作成します。
doAction()メソッドの実装
引き続きpublic/index.phpファイルに、先ほどFastRoute\Dispatcher::FOUNDの処理で呼び出したdoAction()メソッドの中身を書いていきます。
function doAction($handler, $vars)
{
switch ($handler) {
case "get_all_users":
echo "acition users called.";
break;
case "get_user":
echo "action user called. param=" . json_encode($vars);
break;
}
}
doAction()メソッドに渡されるパラメータの1つ目はハンドラで、これはsimpleDispatcher()内のaddRoute()で指定した文字列です。
第1パラメータは、ルーティング中に指定したパラメータ部の展開結果が入ります。先ほどget_userに対して、/user/{id:\d+}
のルーティングを指定していたため
get_userの側が実行視されたとき、$varsにはKeyが'id'でValueがURLで指定されたユーザIDがセットされた連想配列がセットされています。
作ったプログラムの実行
作成したプログラムはphpのビルトインウェブサーバで実行できます。PHPではphpに-Sオプションを指定することで、ApacheやNginxを用意しなくても単体での動作確認ができます。
cd public
php -S localhost:8888 index.php
上記のコマンドでビルトインウェブサーバを実行したのち、ブラウザからhttp://localhost:8888/users
やhttp://localhost:8888/user/123
にアクセスすれば動作を確認できます。それぞれ、下記の出力が行われれば成功です。
http://localhost:8888/usersにアクセスした時の出力結果
http://localhost:8888/user/1にアクセスした時の出力結果
作ったプログラムをApacheで実行する場合
今回作ったプログラムはいわゆるフロントコンローラ形式のプログラムになります。
フロントコンローラ形式のプログラムでは、URLに何を入れられても、今回作ったindex.phpが処理を受け付ける必要があります。
このため、Apacheでは、.htaccessやhttpd.confに下記のRewriteRuleを記載すればよいです。
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [QSA,L]
ルーティング結果のハンドラをクラスのメソッドに割り当てたい
プログラムの作りによっては、例えば/usersがコールされたときにUserクラスのgetUserList()を自動でコールしてほしい場合もあります。
ここでは、クラスのメソッドをディスパッチしたい場合の作り方を説明します。
$dispatcher = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/users', 'User/getAllUsers');
$r->addRoute('GET', '/user/{id:\d+}', 'User/getUser');
});
まず、addRoute()で以下のよう/users
にアクセスされたら'User/getUserList'ハンドラが渡されるようにします。
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
list($class, $method) = explode("/", $handler, 2);
call_user_func_array(array(new $class, $method), [$vars]);
break;
次にルーティング結果で、$handlerを"/"を区切り文字にして、クラスメイトメソッド名にわけけた後、call_user_func_array()で動的なメソッド呼び出しを行います。
class User
{
public function getAllUsers()
{
echo "acition users called.";
}
public function getUser($vars)
{
echo "action user called. param=" . json_encode($vars);
}
}
最後にUserクラスへgetUserList()メソッドを作成すれば、このメソッドがコールされます。