PHP による単純なルーティングの例

このページは、PHP で単純なルーティングを行う例を記載したページです。

注意

  • コードのライセンスは CC0 (クレジット表示不要、改変可、商用可) です。
  • フレームワークを使わない場合に使う用途として想定しています。フレームワークを使用する場合はフレームワークにルーティングの仕組みが組み込まれているため不要です。

コード

.htaccess

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

PHP (index.php)

<?php
/**
 * PATH_INFO の値を取得します。(PATH_INFO が無い場合、REQUEST_URI と SCRIPT_NAME をもとに PATH_INFO 相当の値を生成して返します)
 *
 * @return string PATH_INFO の値 (PATH_INFO が無い場合、PATH_INFO 相当の値)
 */
function path_info() {
    if (isset($_SERVER['PATH_INFO'])) {
        return $_SERVER['PATH_INFO'];
    }
    else if (isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME'])) {
        $url = parse_url('http://example.com' . $_SERVER['REQUEST_URI']);
        if ($url === false) return false;
        return '/' . ltrim(substr($url['path'], strlen(dirname($_SERVER['SCRIPT_NAME']))), '/');
    }
    return false;
}
/**
 * ルーティングを行います。
 *
 * @param array $map ルーティングマップの設定 ( array( array(リクエストメソッド, パスパターン, コールバック), ... )。例: array( array('GET', '/users/:id', 'var_dump') ))
 * @param string $method リクエストされたメソッド。指定しない場合は $_SERVER['REQUEST_METHOD'] が使用されます。
 * @param string $path リクエストされたパス。指定しない場合は $_SERVER['PATH_INFO'] (または $_SERVER['PATH_INFO'] 相当) が使用されます。
 * @return void
 */
function path_route(array $map, $method = null, $path = null) {
    $method = strtoupper(is_null($method) ? $_SERVER['REQUEST_METHOD'] : $method);
    if (is_null($path)) $path = path_info();

    $code = '404';
    $codeMap = array();
    foreach ($map as $item) {
        if (count($item) < 3) continue; // ルーティング項目に値が3つ無い
        $sameMethod = ($item[0] == '*' || strtoupper($item[0]) == $method); // リクエストメソッドに対応するルーティング項目か確認

        if (preg_match('#\A[0-9]{3}\z#', $item[1])) { // エラー用 (404, 405)
            if ($sameMethod) $codeMap[$item[1]] = $item[2];
            continue;
        }
        $pattern = '#\A' . preg_replace('#:([a-zA-Z0-9_]+)#', '(?<$1>[^/]+?)', $item[1]) . '\z#'; // :id → (?<id>[^/]+?)
        if (!preg_match($pattern, $path, $matches)) continue; // パスパターンが一致しない
        if (!$sameMethod) { // メソッドが違う
            $code = '405'; continue; // 405 の可能性あり
        }
        call_user_func($item[2], $matches); // コールバック呼び出し (メソッドとパスパターンが一致)
        return;
    }
    // エラー
    $statusMap = array('404' => 'Not Found', '405' => 'Method Not Allowed');
    header("HTTP/1.1 {$code} {$statusMap[$code]}");
    if (isset($codeMap[$code])) call_user_func($codeMap[$code], array($path));
    else echo $code;
}
function h($value, $encoding = 'UTF-8') { return htmlspecialchars($value, ENT_QUOTES, $encoding); } // HTMlエスケープ出力用
function eh($value, $encoding = 'UTF-8') { echo h($value, $encoding); } // 同上

// 使用例
// ルーティング設定を与えてルーティングを実行します。
path_route(array(
    // array(リクエストメソッド, パスのパターン, 対応関数(またはメソッドなど callable なもの)), という形で設定を与えます。
    array('*', '/', function(){ eh('シンプルなルーティングのテストです。'); }),
    // include() などを使用してもよいです。
    array('GET', '/form/', function(){
        echo '<!DOCTYPE html><html><head><title>test</title></head><body>
            <form method="post"><input type="text" name="name"><input type="submit"></form>
            </body></html>';
    }),
    // POST の例です。
    array('POST', '/form/', function(){ eh(filter_input(INPUT_POST, 'name') . ' が送信されました。'); }),
    // パラメータ付きのURLのルーティングの例です。
    array('GET', '/users/:id', function($params){ eh("ユーザー {$params['id']} さんのページです。"); }),
    // 404 の例です。
    array('*', '404', function(){ echo 'ページが見つかりません。'; })
));