複数の機能を URL から呼べるすごく単純な自分用の何かを作る時などで使えるスニペットです。こういう方法で製品を作ろうとするとセキュリティ面でも作りこみが必要になって返って手間になるかノーガードになるかになりがちなので、外部公開するしっかりしたサービスには Laravel やら Symfony やらのちゃんとしたフレームワークを使った方がいいです。
ルーターのコードが次です。なんとなく配列にルーティング定義を入れてマッチしたところで処理を実行するのがわかるかと思います。
<?php
// ルーター部
foreach ($router as $uri => $uriActions) {
foreach ($uriActions as $method => $action) {
if (preg_match("#\A$uri\z#", $_SERVER['REQUEST_URI'], $matches) && $_SERVER['REQUEST_METHOD'] === $method) {
$action(...array_slice($matches, 1));
exit(0);
}
}
}
実際の使用例兼解説が次です。
<?php
/** @var array $router ルーティング定義 */
$router = [
'/' => [ // 一番浅いキーで URL を正規表現で定義
'GET' => static function () { // 二番目のキーでメソッドを定義
echo 'ここはルートです。'; // 一番奥に URL に対応する処理内容をクロージャで配置
}
],
'/home' => [
'GET' => static function () {
echo 'ここはホームです。';
}
],
'/member/(\d+)' => [
'GET' => static function ($memberId) {
echo "ID: $memberId の詳細ページです。<form method='post'><button type='submit'>send</button></form>";
},
'POST' => static function ($memberId) {
echo "ID: $memberId の更新処理です。";
},
],
'/relation/(\d+)/(\d+)' => [
'GET' => static function ($senderId, $receiverId) {
echo '複数のパラメーターを受け取るページです。'.json_encode(compact('senderId', 'receiverId'));
},
],
];
// ルーティング定義を総ざらいして、マッチした処理を実行する
foreach ($router as $url => $uriActions) {
foreach ($uriActions as $method => $action) {
// #\A$uri\z# の正規表現により、定義した URL の正規表現にリクエストされた URL の最初から最後までが一致する場合のみマッチ
// 正規表現のデリミタに # を用いるのは URL としての # がアンカーへアクセスするための文字で、まず外部公開するページや API そのものの URL として使わないため
// URL がマッチした場合、リクエストのメソッドが定義と一致するか確認する
if (preg_match("#\A$url\z#", $_SERVER['REQUEST_URI'], $matches) && strtoupper($_SERVER['REQUEST_METHOD']) === strtoupper($method)) {
// ルーティング定義とマッチした場合、定義に対応したクロージャを実行する
// preg_match であらかじめ URL ないのパラメーターをグループとして抜き出しておく
// $matches[0] には $_SERVER['REQUEST_URI'] の全体が入っているため、
// $matches[1] 以降を array_slice で切り出して、引数として全体を渡します
// @see https://www.php.net/manual/ja/functions.arguments.php#example-166 PHP: 関数の引数 - Manual#例12 引数での ... の使用例
$action(...array_slice($matches, 1));
exit(0); // 目的の処理が終わったら、これ以上すぐにプロセスを終わらせます。
}
}
}
// どこにもマッチしなかった場合、404 を返します。
header('Status: 404 Not Found');
echo '404ページです';
exit(0);
この状態で / や /home や /member/3 にアクセスするとそれぞれに対応した処理が走ります。ここまで作ったらあとは web サーバーで実体のない URL へのアクセスを全てルーターの PHP ファイルに向ければ完成です。この設定は次でできます。次は nginxの設定です。
# hoge.conf
server {
# nginx 設定例
listen 80; # ポート
root /work/backend/public; # ドキュメントルート
index index.php; # インデックスファイルの定義
charset utf-8; # 使用文字コード
location / {
# 実体のあるファイル、ディレクトリを読みに行き、
# なければドキュメントルート直下の index.php に処理をさせます
try_files $uri $uri/ /index.php$is_args$args;
}
# PHP と nginx の接続設定
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
Apacheは次です。
# hoge.conf # 製品環境で使うならログを取ったり、セキュリティ面だったりでもっと増えます。 <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /work/backend/public </VirtualHost>
# .htaccess 設定は laravel/laravel のそれが使えるのでそのまま引用しています
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
# MultiVies と Indexes の二つを無効化しています。
# この二つを動かそうとしたところ 403 になりましたので↓は説明書を読んでの推測の説明です。
# MultiViews は拡張抜きのリクエストがあったら、拡張子込みで探索して結果を返します
# /path/dir/foo で foo が見つからない場合、foo.* を探して foo.txt を返す感じです
# Indexes はディレクトリの中身を整形して返します。
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# PHPに Authorization ヘッダを渡すらしいです
# 認証が関係ないシステムなら恐らく不要です
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# フォルダでないモノの末尾に / がついていたら / を消してリダイレクト
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# リクエストを index.php に渡す
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>