Completed
Push — master ( ea4399...b57abd )
by Alex
01:44
created

UrlParser.php (2 issues)

1
<?php
2
namespace Mezon\Router;
3
4
class 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
     * Constructor
86
     */
87
    public function __construct()
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
        $return = '';
0 ignored issues
show
The assignment to $return is dead and can be removed.
Loading history...
108
109
        if (isset($this->types[$parameterData[0]])) {
110
            if ($this->types[$parameterData[0]]($component)) {
111
                return $parameterData[1];
112
            } else {
113
                return '';
114
            }
115
        } else {
116
            throw (new \Exception('Unknown parameter type : ' . $parameterData[0]));
117
        }
118
119
        return $return;
0 ignored issues
show
return $return is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
120
    }
121
122
    /**
123
     * Method matches route and pattern
124
     *
125
     * @param array $cleanRoute
126
     *            Cleaned route splitted in parts
127
     * @param array $cleanPattern
128
     *            Route pattern
129
     * @return array|bool Array of route's parameters
130
     */
131
    private function _matchRouteAndPattern(array $cleanRoute, array $cleanPattern)
132
    {
133
        if (count($cleanRoute) !== count($cleanPattern)) {
134
            return false;
135
        }
136
137
        $paremeters = [];
138
        $patternsCount = count($cleanPattern);
139
140
        for ($i = 0; $i < $patternsCount; $i ++) {
141
            if (\Mezon\Router\Utils::isParameter($cleanPattern[$i])) {
142
                $parameterName = $this->_matchParameterAndComponent($cleanRoute[$i], $cleanPattern[$i]);
143
144
                // it's a parameter
145
                if ($parameterName !== '') {
146
                    // parameter was matched, store it!
147
                    $paremeters[$parameterName] = $cleanRoute[$i];
148
                } else {
149
                    return false;
150
                }
151
            } else {
152
                // it's a static part of the route
153
                if ($cleanRoute[$i] !== $cleanPattern[$i]) {
154
                    return false;
155
                }
156
            }
157
        }
158
159
        $this->parameters = $paremeters;
160
    }
161
162
    /**
163
     * Method searches dynamic route processor
164
     *
165
     * @param array $processors
166
     *            Callable router's processor
167
     * @param string $route
168
     *            Route
169
     * @return string|bool Result of the router'scall or false if any error occured
170
     */
171
    public function findDynamicRouteProcessor(array &$processors, string $route)
172
    {
173
        $cleanRoute = explode('/', trim($route, '/'));
174
175
        foreach ($processors as $i => $processor) {
176
            $cleanPattern = explode('/', trim($i, '/'));
177
178
            if ($this->_matchRouteAndPattern($cleanRoute, $cleanPattern) !== false) {
179
                return call_user_func($processor, $route, $this->parameters); // return result of the router
180
            }
181
        }
182
183
        return false;
184
    }
185
186
    /**
187
     * Checking that method exists
188
     *
189
     * @param object|array $processor
190
     *            callback object
191
     * @param ?string $functionName
192
     *            callback method
193
     * @return bool true if method does not exists
194
     */
195
    private function methodDoesNotExists($processor, ?string $functionName): bool
196
    {
197
        return isset($processor[0]) && method_exists($processor[0], $functionName) === false;
198
    }
199
200
    /**
201
     * Checking that handler can be called
202
     *
203
     * @param object|array $processor
204
     *            callback object
205
     * @param ?string $functionName
206
     *            callback method
207
     * @return bool
208
     */
209
    private function canBeCalled($processor, ?string $functionName): bool
210
    {
211
        return is_callable($processor) &&
212
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName));
213
    }
214
215
    /**
216
     * Checking that processor can be called as function
217
     *
218
     * @param mixed $processor
219
     *            route processor
220
     * @return bool true if the $processor can be called as function
221
     */
222
    private function isFunction($processor): bool
223
    {
224
        return is_callable($processor) && is_array($processor) === false;
225
    }
226
227
    /**
228
     * Method searches route processor
229
     *
230
     * @param mixed $processors
231
     *            Callable router's processor
232
     * @param string $route
233
     *            Route
234
     * @return mixed Result of the router processor
235
     */
236
    public function findStaticRouteProcessor(&$processors, string $route)
237
    {
238
        foreach ($processors as $i => $processor) {
239
            // exact router or 'all router'
240
            if ($i == $route || $i == '/*/') {
241
                if ($this->isFunction($processor)) {
242
                    return $processor($route, []);
243
                }
244
245
                $functionName = $processor[1] ?? null;
246
247
                if ($this->canBeCalled($processor, $functionName)) {
248
                    // passing route path and parameters
249
                    return call_user_func($processor, $route, []);
250
                } else {
251
                    $callableDescription = \Mezon\Router\Utils::getCallableDescription($processor);
252
253
                    if ($this->methodDoesNotExists($processor, $functionName)) {
254
                        throw (new \Exception("'$callableDescription' does not exists"));
255
                    } else {
256
                        throw (new \Exception("'$callableDescription' must be callable entity"));
257
                    }
258
                }
259
            }
260
        }
261
262
        return false;
263
    }
264
265
    /**
266
     * Method returns route parameter
267
     *
268
     * @param string $name
269
     *            Route parameter
270
     * @return string Route parameter
271
     */
272
    public function getParam(string $name): string
273
    {
274
        if (isset($this->parameters[$name]) === false) {
275
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
276
        }
277
278
        return $this->parameters[$name];
279
    }
280
281
    /**
282
     * Does parameter exists
283
     *
284
     * @param string $name
285
     *            Param name
286
     * @return bool True if the parameter exists
287
     */
288
    public function hasParam(string $name): bool
289
    {
290
        return isset($this->parameters[$name]);
291
    }
292
}