CommonPrefixTrieRouter   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 71
c 2
b 0
f 0
dl 0
loc 128
ccs 0
cts 88
cp 0
rs 10
wmc 28

2 Methods

Rating   Name   Duplication   Size   Complexity  
C trieConstruction() 0 58 16
C search() 0 36 12
1
<?php
2
3
namespace Teto\Routing;
4
5
use function ord;
6
use function strlen;
7
use function substr;
8
use function sprintf;
9
10
/**
11
 * 共通接頭辞木構造を連想配列で実装したRouter
12
 *
13
 * @copyright 2015 Yusuke Koashi
14
 * @license MIT
15
 * @see https://gist.github.com/neo-nanikaka/c2e2f7742b311696d50b
16
 * @see http://inside.pixiv.net/entry/2015/12/13/145741
17
 */
18
final class CommonPrefixTrieRouter
19
{
20
    const URL_PARAMETER_TYPE_NUM = '[';
21
    const URL_PARAMETER_TYPE_STRING = ']';
22
23
    /** @var string ルーティングが存在するノードにおいて、値はこのキーで引く */
24
    private static $VALID_STATE_MARK = '>';
25
    /** @var string URLパラメータがあった場合、このキーで引く */
26
    private static $URL_PARAMETER_NAME = 'name';
27
28
    /**
29
     * ルーティング決定のための探索を行う
30
     *
31
     * @param array  $trie        指定の形式の連想配列
32
     * @param string $request_uri 解析したいURL
33
     * @param string $http_method HTTPメソッド
34
     *
35
     * @return array [
36
     *   'value'  => ルーティングの結果。値はなんでもよい
37
     *   'params' => [
38
     *     'user_id' => '12345', // URLパラメータがあればその値を連想配列にする
39
     *   ]
40
     * ]
41
     */
42
    public static function search($trie, $request_uri, $http_method)
43
    {
44
        $p = $trie[$http_method];
45
46
        $length = strlen($request_uri);
47
        $i = 0;
48
        $ok = (0 < $length); // request_uriが空文字列だった場合にnullを返せるように
49
        $result = []; // URLパラメータの値を記憶しておく変数
50
        while ($i < $length) {
51
            if ($request_uri[$i] !== '/') {
52
                $ok = false;
53
                break;
54
            }
55
            $str = '' . $request_uri[$i++];
56
            $num_only = true;
57
            while ($i < $length && $request_uri[$i] !== '/') {
58
                $str .= $request_uri[$i];
59
                $x = ord($request_uri[$i]);
60
                $num_only &= (48 <= $x && $x <= 57);
61
                $i++;
62
            }
63
            if (isset($p[$str])) {
64
                $p = $p[$str];
65
            } elseif ($num_only && isset($p[self::URL_PARAMETER_TYPE_NUM])) {
66
                $p = $p[self::URL_PARAMETER_TYPE_NUM];
67
                $result[$p[self::$URL_PARAMETER_NAME]] = substr($str, 1);
68
            } elseif (isset($p[self::URL_PARAMETER_TYPE_STRING])) {
69
                $p = $p[self::URL_PARAMETER_TYPE_STRING];
70
                $result[$p[self::$URL_PARAMETER_NAME]] = substr($str, 1);
71
            } else {
72
                $ok = false;
73
                break;
74
            }
75
        }
76
        return $ok && isset($p[self::$VALID_STATE_MARK]) ? ['value' => $p[self::$VALID_STATE_MARK], 'params' => $result]
77
                                                         : null;
78
    }
79
80
    /**
81
     * Trie木を表現した連想配列を構築する
82
     *
83
     * 動的に木を組み立てるために参照 & を多用している
84
     *
85
     * @param array $conf
86
     * @return array
87
     */
88
    public static function trieConstruction(array $conf)
89
    {
90
        $trie = [];
91
        foreach ($conf as $con) {
92
            $http_method = $con[0];
93
            $path = $con[1];
94
            $value = $con[2];
95
            $param_mapping = isset($con[3]) ? $con[3] : [];
96
97
            if (!isset($trie[$http_method])) {
98
                $trie[$http_method] = [];
99
            }
100
101
            $node = &$trie[$http_method];
102
103
            $path_length = strlen($path);
104
            $i = 0;
105
            while ($i < $path_length) {
106
                if ($path[$i++] !== '/') {
107
                    throw new \Exception(sprintf("不正なパスが設定されています %s", $path));
108
                }
109
                $partial_path = '/';
110
                while ($i < $path_length && $path[$i] !== '/') {
111
                    $partial_path .= $path[$i++];
112
                }
113
114
                $is_url_parameter = false;
115
                $url_param_name = null;
116
                // URLパラメータだった場合
117
                if (1 < strlen($partial_path) && $partial_path[1] === ':') {
118
                    $is_url_parameter = true;
119
                    $url_param_name = substr($partial_path, 2);
120
                    if (!isset($param_mapping[$url_param_name])) {
121
                        throw new \Exception(sprintf("URLパラメータ :%s に対する設定が足りません", $url_param_name));
122
                    }
123
                    $partial_path = $param_mapping[$url_param_name];
124
                }
125
126
                if (!isset($node[$partial_path])) {
127
                    if ($is_url_parameter) {
128
                        $node[$partial_path] = [self::$URL_PARAMETER_NAME => $url_param_name];
129
                    } else {
130
                        $node[$partial_path] = [];
131
                    }
132
                } else {
133
                    if ($is_url_parameter && $node[$partial_path][self::$URL_PARAMETER_NAME] !== $url_param_name) {
134
                        throw new \Exception(sprintf("URLパラメータに別名をつけようとしています %s (:%s, :%s)", $path, $url_param_name, $node[$partial_path][self::$URL_PARAMETER_NAME]));
135
                    }
136
                }
137
                $node = &$node[$partial_path];
138
            }
139
            if (isset($node[self::$VALID_STATE_MARK])) {
140
                throw new \Exception(sprintf("重複したルーティングルールがあります %s", $path));
141
            }
142
            $node[self::$VALID_STATE_MARK] = $value;
143
        }
144
145
        return $trie;
146
    }
147
}
148