Files
LittleBeast/src/Site/Lib/Route.php
2025-11-07 22:48:07 +09:00

163 lines
4.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Site\Lib;
class Route {
protected static array $routes = [];
protected static array $fallback = [];
/**
* ルート設定を言語固有のハンドラで初期化する
*
* @param array $routes ルート設定の配列
* @return void
*/
public static function init(array $routes): void {
self::$routes = $routes;
}
/**
* ルートを追加する
*
* @param string $method HTTPメソッド
* @param string $path URLパス
* @param string|callable $class ハンドラクラスとメソッド、またはコールバック
* @param array $params オプションのパラメータ
* @return array ルート設定
*/
public static function add(string $method, string $path, string|callable $class,
array $params = []): array {
$route = [
'method' => $method,
'path' => $path,
'class' => $class,
'params' => $params,
];
self::$routes[] = $route;
return $route;
}
/**
* 404処理用のフォールバックルートを設定する
*
* @param array|string|callable $class
* @return void
*/
public static function setFallback(array|string|callable $class): void {
self::$fallback = [
'class' => $class,
'params' => [],
];
}
/**
* 適切なルートをマッチさせて実行する
*
* @param string $uri リクエストURI
* @return void
*/
public static function dispatch(string $uri): void {
// URIをパスとクエリ文字列に分割
$uriParts = explode('?', $uri, 2);
$path = trim($uriParts[0], " \t\n\r\0\x0B/");
// ルートパスの処理(/?page=2のようなクエリパラメータを含む場合も処理
if ($path === '') {
self::executeClass([
'class' => [new \Site\Controller\Home(), 'show'],
'params' => ['lang' => 'ja'],
]);
return;
}
if ($path === 'en') {
self::executeClass([
'class' => [new \Site\Controller\Home(), 'show'],
'params' => ['lang' => 'en'],
]);
return;
}
// パスに対してルートをマッチングする
foreach (self::$routes as $route) {
$matches = [];
if (self::matchRoute($route['path'], $path, $matches)) {
$params = self::extractParams($route['path'], $path);
$params = array_merge($route['params'], $params);
if (is_string($route['class'])) {
[ $class, $method ] = explode('@', $route['class']);
$controller = new $class();
self::executeClass([
'class' => [ $controller, $method ],
'params' => $params,
]);
return;
} elseif (is_callable($route['class'])) {
self::executeClass([
'class' => $route['class'],
'params' => $params,
]);
return;
}
}
}
// マッチするルートがない場合、フォールバックを実行
self::executeClass(self::$fallback);
}
/**
* ルートパターンとパスをマッチングする
*
* @param string $pattern ルートパターン
* @param string $path 現在のパス
* @param array $matches マッチを格納する参照
* @return bool
*/
protected static function matchRoute(string $pattern, string $path,
array &$matches = []): bool {
// ルートパターンを正規表現パターンに変換
$pattern = preg_replace('/\{([^:}]+)(?::([^}]+))?\}/', '(?P<$1>[^/]+)', $pattern);
$pattern = str_replace('/', '\/', $pattern);
return (bool)preg_match('/^'.$pattern.'$/', $path, $matches);
}
/**
* パターンに基づいてパスから名前付きパラメータを抽出する
*
* @param string $pattern ルートパターン
* @param string $path 現在のパス
* @return array
*/
protected static function extractParams(string $pattern, string $path): array {
$params = [];
$patternParts = explode('/', $pattern);
$pathParts = explode('/', $path);
foreach ($patternParts as $k => $v) {
if (preg_match('/\{([^:}]+)(?::([^}]+))?\}/', $v, $matches)) {
if (isset($pathParts[$k])) {
$params[$matches[1]] = $pathParts[$k];
}
}
}
return $params;
}
/**
* ルートクラスを実行する
*
* @param array $route ルート設定
* @return void
*/
protected static function executeClass(array $route): void {
if (is_callable($route['class'])) {
call_user_func($route['class'], $route['params'] ?? []);
}
}
}