Completed
Push — master ( 1d0b60...66f8c9 )
by Alex
07:16
created

UrlParser::initDefaultTypes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
1
<?php
2
namespace Mezon\Router;
3
4
trait UrlParser
5
{
6
7
    /**
8
     * Supported types of URL parameters
9
     *
10
     * @var array
11
     */
12
    private $types = [];
13
14
    /**
15
     * Parsed parameters of the calling router
16
     *
17
     * @var array
18
     */
19
    protected $parameters = [];
20
21
    /**
22
     * Method handles integer type
23
     *
24
     * @param string $value
25
     *            value to be parsed
26
     * @return bool was the value parsed
27
     */
28
    public static function intHandler(string &$value): bool
29
    {
30
        if (is_numeric($value)) {
31
            $value = $value + 0;
32
            return true;
33
        }
34
35
        return false;
36
    }
37
38
    /**
39
     * Method handles command type
40
     *
41
     * @param string $value
42
     *            value to be parsed
43
     * @return bool was the value parsed
44
     */
45
    public static function commandHandler(string &$value): bool
46
    {
47
        if (preg_match('/^([a-z0-9A-Z_\/\-\.\@]+)$/', $value)) {
48
            return true;
49
        }
50
51
        return false;
52
    }
53
54
    /**
55
     * Method handles list of integers type
56
     *
57
     * @param string $value
58
     *            value to be parsed
59
     * @return bool was the value parsed
60
     */
61
    public static function intListHandler(string &$value): bool
62
    {
63
        if (preg_match('/^([0-9,]+)$/', $value)) {
64
            return true;
65
        }
66
67
        return false;
68
    }
69
70
    /**
71
     * Method handles string type
72
     *
73
     * @param string $value
74
     *            value to be parsed
75
     * @return bool was the value parsed
76
     */
77
    public static function stringHandler(string &$value): bool
78
    {
79
        $value = htmlspecialchars($value, ENT_QUOTES);
80
81
        return true;
82
    }
83
84
    /**
85
     * Init types
86
     */
87
    protected function initDefaultTypes()
88
    {
89
        $this->types['i'] = '\Mezon\Router\UrlParser::intHandler';
90
        $this->types['a'] = '\Mezon\Router\UrlParser::commandHandler';
91
        $this->types['il'] = '\Mezon\Router\UrlParser::intListHandler';
92
        $this->types['s'] = '\Mezon\Router\UrlParser::stringHandler';
93
    }
94
95
    /**
96
     * Matching parameter and component
97
     *
98
     * @param mixed $component
99
     *            Component of the URL
100
     * @param string $parameter
101
     *            Parameter to be matched
102
     * @return string Matched url parameter
103
     */
104
    private function _matchParameterAndComponent(&$component, string $parameter)
105
    {
106
        $parameterData = explode(':', trim($parameter, '[]'));
107
108
        if (isset($this->types[$parameterData[0]])) {
109
            if ($this->types[$parameterData[0]]($component)) {
110
                return $parameterData[1];
111
            } else {
112
                return '';
113
            }
114
        } else {
115
            throw (new \Exception('Unknown parameter type : ' . $parameterData[0]));
116
        }
117
    }
118
119
    /**
120
     * Method matches route and pattern
121
     *
122
     * @param array $cleanRoute
123
     *            Cleaned route splitted in parts
124
     * @param array $cleanPattern
125
     *            Route pattern
126
     * @return array|bool Array of route's parameters
127
     */
128
    private function _matchRouteAndPattern(array $cleanRoute, array $cleanPattern)
129
    {
130
        if (count($cleanRoute) !== count($cleanPattern)) {
131
            return false;
132
        }
133
134
        $paremeters = [];
135
        $patternsCount = count($cleanPattern);
136
137
        for ($i = 0; $i < $patternsCount; $i ++) {
138
            if (\Mezon\Router\Utils::isParameter($cleanPattern[$i])) {
139
                $parameterName = $this->_matchParameterAndComponent($cleanRoute[$i], $cleanPattern[$i]);
140
141
                // it's a parameter
142
                if ($parameterName !== '') {
143
                    // parameter was matched, store it!
144
                    $paremeters[$parameterName] = $cleanRoute[$i];
145
                } else {
146
                    return false;
147
                }
148
            } else {
149
                // it's a static part of the route
150
                if ($cleanRoute[$i] !== $cleanPattern[$i]) {
151
                    return false;
152
                }
153
            }
154
        }
155
156
        $this->parameters = $paremeters;
157
    }
158
159
    /**
160
     * Method searches dynamic route processor
161
     *
162
     * @param array $processors
163
     *            Callable router's processor
164
     * @param string $route
165
     *            Route
166
     * @return string|bool Result of the router'scall or false if any error occured
167
     */
168
    public function findDynamicRouteProcessor(array &$processors, string $route)
169
    {
170
        $cleanRoute = explode('/', trim($route, '/'));
171
172
        foreach ($processors as $i => $processor) {
173
            $cleanPattern = explode('/', trim($i, '/'));
174
175
            if ($this->_matchRouteAndPattern($cleanRoute, $cleanPattern) !== false) {
176
                return call_user_func($processor, $route, $this->parameters); // return result of the router
177
            }
178
        }
179
180
        return false;
181
    }
182
183
    /**
184
     * Checking that method exists
185
     *
186
     * @param object|array $processor
187
     *            callback object
188
     * @param ?string $functionName
189
     *            callback method
190
     * @return bool true if method does not exists
191
     */
192
    private function methodDoesNotExists($processor, ?string $functionName): bool
193
    {
194
        return isset($processor[0]) && method_exists($processor[0], $functionName) === false;
195
    }
196
197
    /**
198
     * Checking that handler can be called
199
     *
200
     * @param object|array $processor
201
     *            callback object
202
     * @param ?string $functionName
203
     *            callback method
204
     * @return bool
205
     */
206
    private function canBeCalled($processor, ?string $functionName): bool
207
    {
208
        return is_callable($processor) &&
209
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName));
210
    }
211
212
    /**
213
     * Checking that processor can be called as function
214
     *
215
     * @param mixed $processor
216
     *            route processor
217
     * @return bool true if the $processor can be called as function
218
     */
219
    private function isFunction($processor): bool
220
    {
221
        return is_callable($processor) && is_array($processor) === false;
222
    }
223
224
    /**
225
     * Does the handler fits to route
226
     *
227
     * @param string $i
228
     *            handler
229
     * @param string $route
230
     *            route
231
     * @return bool true if the handler fits tto route
232
     */
233
    private function routeFits(string $i, string $route): bool
234
    {
235
        return $i == $route || $i == '/*/';
236
    }
237
238
    /**
239
     * Method searches route processor
240
     *
241
     * @param mixed $processors
242
     *            Callable router's processor
243
     * @param string $route
244
     *            Route
245
     * @return mixed Result of the router processor
246
     */
247
    public function findStaticRouteProcessor(&$processors, string $route)
248
    {
249
        foreach ($processors as $i => $processor) {
250
            // exact router or 'all router'
251
            if ($this->routeFits($i, $route)) {
252
                if ($this->isFunction($processor)) {
253
                    return $processor($route, []);
254
                }
255
256
                $functionName = $processor[1] ?? null;
257
258
                if ($this->canBeCalled($processor, $functionName)) {
259
                    // passing route path and parameters
260
                    return call_user_func($processor, $route, []);
261
                } else {
262
                    $callableDescription = \Mezon\Router\Utils::getCallableDescription($processor);
263
264
                    if ($this->methodDoesNotExists($processor, $functionName)) {
265
                        throw (new \Exception("'$callableDescription' does not exists"));
266
                    } else {
267
                        throw (new \Exception("'$callableDescription' must be callable entity"));
268
                    }
269
                }
270
            }
271
        }
272
273
        return false;
274
    }
275
276
    /**
277
     * Method returns route parameter
278
     *
279
     * @param string $name
280
     *            Route parameter
281
     * @return string Route parameter
282
     */
283
    public function getParam(string $name): string
284
    {
285
        if (isset($this->parameters[$name]) === false) {
286
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
287
        }
288
289
        return $this->parameters[$name];
290
    }
291
292
    /**
293
     * Does parameter exists
294
     *
295
     * @param string $name
296
     *            Param name
297
     * @return bool True if the parameter exists
298
     */
299
    public function hasParam(string $name): bool
300
    {
301
        return isset($this->parameters[$name]);
302
    }
303
}