Passed
Push — master ( 358f06...e78b35 )
by Alex
02:17 queued 27s
created

UrlParser.php (1 issue)

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

303
        /** @scrutinizer ignore-call */ 
304
        $route = $this->getRouteByName($routeName);
Loading history...
304
305
        foreach ($parameters as $name => $value) {
306
            $route = preg_replace('/\[([A-Za-z_\-])\:' . $name . ']/', $value, $route);
307
        }
308
309
        return $route;
310
    }
311
}
312