Skip to content

Instantly share code, notes, and snippets.

@neo-nanikaka
Last active December 24, 2022 23:28
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neo-nanikaka/c2e2f7742b311696d50b to your computer and use it in GitHub Desktop.
Save neo-nanikaka/c2e2f7742b311696d50b to your computer and use it in GitHub Desktop.
CommonPrefixTrieRouter.php
<?php
/**
* 共通接頭辞木構造を連想配列で実装したRouter
* @copyright 2015 Yusuke Koashi
* @license MIT
*/
final class CommonPrefixTrieRouter
{
const URL_PARAMETER_TYPE_NUM = '[';
const URL_PARAMETER_TYPE_STRING = ']';
private static $VALID_STATE_MARK = '>'; // ルーティングが存在するノードにおいて、値はこのキーで引く
private static $URL_PARAMETER_NAME = 'name'; // URLパラメータがあった場合、このキーで引く
/**
* ルーティング決定のための探索を行う
*
* @param array $trie 指定の形式の連想配列
* @param string $request_uri 解析したいURL
* @param string $http_method HTTPメソッド
*
* @return array [
* 'value' => ルーティングの結果。値はなんでもよい
* 'params' => [
* 'user_id' => '12345', // URLパラメータがあればその値を連想配列にする
* ]
* ]
*/
public static function search($trie, $request_uri, $http_method)
{
$p = $trie[$http_method];
$length = strlen($request_uri);
$i = 0;
$ok = (0 < $length); // request_uriが空文字列だった場合にnullを返せるように
$result = []; // URLパラメータの値を記憶しておく変数
while ($i < $length) {
if ($request_uri[$i] !== '/') {
$ok = false;
break;
}
$str = '' . $request_uri[$i++];
$num_only = true;
while ($i < $length && $request_uri[$i] !== '/') {
$str .= $request_uri[$i];
$x = ord($request_uri[$i]);
$num_only &= (48 <= $x && $x <= 57);
$i++;
}
if (isset($p[$str])) {
$p = $p[$str];
} elseif ($num_only && isset($p[self::URL_PARAMETER_TYPE_NUM])) {
$p = $p[self::URL_PARAMETER_TYPE_NUM];
$result[$p[self::$URL_PARAMETER_NAME]] = substr($str, 1);
} elseif (isset($p[self::URL_PARAMETER_TYPE_STRING])) {
$p = $p[self::URL_PARAMETER_TYPE_STRING];
$result[$p[self::$URL_PARAMETER_NAME]] = substr($str, 1);
} else {
$ok = false;
break;
}
}
return $ok && isset($p[self::$VALID_STATE_MARK]) ? ['value' => $p[self::$VALID_STATE_MARK], 'params' => $result]
: null;
}
/**
* Trie木を表現した連想配列を構築する
*
* 動的に木を組み立てるために参照 & を多用している
*
* @param array $conf
* @return array
*/
public static function trieConstruction(array $conf)
{
$trie = [];
foreach ($conf as $con) {
$http_method = $con[0];
$path = $con[1];
$value = $con[2];
$param_mapping = isset($con[3]) ? $con[3] : [];
if (!isset($trie[$http_method])) {
$trie[$http_method] = [];
}
$node = &$trie[$http_method];
$path_length = strlen($path);
$i = 0;
while ($i < $path_length) {
if ($path[$i++] !== '/') {
throw new Exception(sprintf("不正なパスが設定されています %s", $path));
}
$partial_path = '/';
while ($i < $path_length && $path[$i] !== '/') {
$partial_path .= $path[$i++];
}
$is_url_parameter = false;
$url_param_name = null;
// URLパラメータだった場合
if (1 < strlen($partial_path) && $partial_path[1] === ':') {
$is_url_parameter = true;
$url_param_name = substr($partial_path, 2);
if (!isset($param_mapping[$url_param_name])) {
throw new Exception(sprintf("URLパラメータ :%s に対する設定が足りません", $url_param_name));
}
$partial_path = $param_mapping[$url_param_name];
}
if (!isset($node[$partial_path])) {
if ($is_url_parameter) {
$node[$partial_path] = [self::$URL_PARAMETER_NAME => $url_param_name];
} else {
$node[$partial_path] = [];
}
} else {
if ($is_url_parameter && $node[$partial_path][self::$URL_PARAMETER_NAME] !== $url_param_name) {
throw new Exception(sprintf("URLパラメータに別名をつけようとしています %s (:%s, :%s)", $path, $url_param_name, $node[$partial_path][self::$URL_PARAMETER_NAME]));
}
}
$node = &$node[$partial_path];
}
if (isset($node[self::$VALID_STATE_MARK])) {
throw new Exception(sprintf("重複したルーティングルールがあります %s", $path));
}
$node[self::$VALID_STATE_MARK] = $value;
}
return $trie;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment