aboutsummaryrefslogtreecommitdiff
path: root/engine/Router.php
blob: 0cb761d2b0ddbcdf5022c00e71c7fb046f70f47e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?php

class Router {

    protected array $routes = [
        'children' => [],
        're_children' => []
    ];

    public function add($template, $value) {
        if ($template == '')
            return $this;

        // expand {enum,erat,ions}
        $templates = [[$template, $value]];
        if (preg_match_all('/\{([\w\d_\-,]+)\}/', $template, $matches)) {
            foreach ($matches[1] as $match_index => $variants) {
                $variants = explode(',', $variants);
                $variants = array_map('trim', $variants);
                $variants = array_filter($variants, function($s) { return $s != ''; });

                for ($i = 0; $i < count($templates); ) {
                    list($template, $value) = $templates[$i];
                    $new_templates = [];
                    foreach ($variants as $variant_index => $variant) {
                        $new_templates[] = [
                            str_replace_once($matches[0][$match_index], $variant, $template),
                            str_replace('${'.($match_index+1).'}', $variant, $value)
                        ];
                    }
                    array_splice($templates, $i, 1, $new_templates);
                    $i += count($new_templates);
                }
            }
        }

        // process all generated routes
        foreach ($templates as $template) {
            list($template, $value) = $template;

            $start_pos = 0;
            $parent = &$this->routes;
            $template_len = strlen($template);

            while ($start_pos < $template_len) {
                $slash_pos = strpos($template, '/', $start_pos);
                if ($slash_pos !== false) {
                    $part = substr($template, $start_pos, $slash_pos-$start_pos+1);
                    $start_pos = $slash_pos+1;
                } else {
                    $part = substr($template, $start_pos);
                    $start_pos = $template_len;
                }

                $parent = &$this->_addRoute($parent, $part,
                    $start_pos < $template_len ? null : $value);
            }
        }

        return $this;
    }

    protected function &_addRoute(&$parent, $part, $value = null) {
        $par_pos = strpos($part, '(');
        $is_regex = $par_pos !== false && ($par_pos == 0 || $part[$par_pos-1] != '\\');

        $children_key = !$is_regex ? 'children' : 're_children';

        if (isset($parent[$children_key][$part])) {
            if (is_null($value)) {
                $parent = &$parent[$children_key][$part];
            } else {
                if (!isset($parent[$children_key][$part]['value'])) {
                    $parent[$children_key][$part]['value'] = $value;
                } else {
                    trigger_error(__METHOD__.': route is already defined');
                }
            }
            return $parent;
        }

        $child = [
            'children' => [],
            're_children' => []
        ];
        if (!is_null($value))
            $child['value'] = $value;

        $parent[$children_key][$part] = $child;
        return $parent[$children_key][$part];
    }

    public function find($uri) {
        if ($uri != '/' && $uri[0] == '/') {
            $uri = substr($uri, 1);
        }
        $start_pos = 0;
        $parent = &$this->routes;
        $uri_len = strlen($uri);
        $matches = [];

        while ($start_pos < $uri_len) {
            $slash_pos = strpos($uri, '/', $start_pos);
            if ($slash_pos !== false) {
                $part = substr($uri, $start_pos, $slash_pos-$start_pos+1);
                $start_pos = $slash_pos+1;
            } else {
                $part = substr($uri, $start_pos);
                $start_pos = $uri_len;
            }

            $found = false;
            if (isset($parent['children'][$part])) {
                $parent = &$parent['children'][$part];
                $found = true;
            } else if (!empty($parent['re_children'])) {
                foreach ($parent['re_children'] as $re => &$child) {
                    $exp = '#^'.$re.'$#';
                    $re_result = preg_match($exp, $part, $match);
                    if ($re_result === false) {
                        logError(__METHOD__.": regex $exp failed");
                        continue;
                    }

                    if ($re_result) {
                        if (count($match) > 1) {
                            $matches = array_merge($matches, array_slice($match, 1));
                        }
                        $parent = &$child;
                        $found = true;
                        break;
                    }
                }
            }

            if (!$found)
                return false;
        }

        if (!isset($parent['value']))
            return false;

        $value = $parent['value'];
        if (!empty($matches)) {
            foreach ($matches as $i => $match) {
                $needle = '$('.($i+1).')';
                $pos = strpos($value, $needle);
                if ($pos !== false) {
                    $value = substr_replace($value, $match, $pos, strlen($needle));
                }
            }
        }

        return $value;
    }

    public function load($routes) {
        $this->routes = $routes;
    }

    public function dump(): array {
        return $this->routes;
    }

}