$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'] ?? []); } } }