Completed
Push — master ( 369737...64c6ea )
by Alex
07:19
created

UrlParser::_getParameterNames()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 21
rs 9.9332
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
     * Cache for regular expressions
16
     * 
17
     * @var array
18
     */
19
    private $cachedRegExps = [];
20
21
    /**
22
     * Method compiles route pattern string in regex string.
23
     * For example [i:id]/some-str in ([\[0-9\]])/some-str
24
     *
25
     * @param string $routerPattern
26
     *            router pattern
27
     * @return string regexp pattern
28
     */
29
    private function _getRouteMatcherRegExPattern(string $routerPattern): string
30
    {
31
        // try read from cache
32
        if (isset($this->cachedRegExps[$routerPattern])) {
33
            return $this->cachedRegExps[$routerPattern];
34
        }
35
36
        // parsing routes
37
        $compiledRouterPattern = $routerPattern;
38
        foreach ($this->types as $typeClass) {
39
            $compiledRouterPattern = preg_replace(
40
                '/' . $typeClass::searchRegExp() . '/',
41
                $typeClass::parserRegExp(),
42
                $compiledRouterPattern);
43
        }
44
45
        // final setup + save in cache
46
        $this->cachedRegExps[$routerPattern] = $compiledRouterPattern;
47
48
        // TODO and we need to store this cache while dumping
49
        return $compiledRouterPattern;
50
    }
51
52
    /**
53
     * Method returns all parameter names in the route
54
     *
55
     * @param string $routerPattern
56
     *            route
57
     * @return array names
58
     */
59
    private function _getParameterNames(string $routerPattern): array
60
    {
61
        $regExPattern = [];
62
63
        foreach (array_keys($this->types) as $typeName) {
64
            $regExPattern[] = $typeName;
65
        }
66
67
        $regExPattern = '\[(' . implode('|', $regExPattern) . '):([a-zA-Z0-9_\-]+)\]';
68
69
        $names = [];
70
        preg_match_all('/' . str_replace('/', '\\/', $regExPattern) . '/', $routerPattern, $names);
71
72
        $return = [];
73
74
        foreach ($names[2] as $name) {
75
            $return[] = $name;
76
        }
77
78
        // TODO cache this value
79
        return $return;
80
    }
81
82
    /**
83
     * Method searches dynamic route processor
84
     *
85
     * @param array $processors
86
     *            Callable router's processor
87
     * @param string $route
88
     *            Route
89
     * @return array|callable|bool route's handler or false in case the handler was not found
90
     */
91
    protected function getDynamicRouteProcessor(array &$processors, string $route)
92
    {
93
        $values = [];
94
95
        foreach ($processors as $pattern => $processor) {
96
            // may be it is tatic route?
97
            if (strpos($pattern, '[') === false) {
98
                // it is static route, so skip it
99
                continue;
100
            }
101
102
            $regExPattern = $this->_getRouteMatcherRegExPattern($pattern);
103
104
            // try match
105
            if (preg_match('/^' . str_replace('/', '\\/', $regExPattern) . '$/', $route, $values)) {
106
                // fetch parameter names
107
                $names = $this->_getParameterNames($pattern);
108
109
                $this->parameters = [];
110
                foreach ($names as $i => $name) {
111
                    $this->parameters[$name] = $values[$i + 1];
112
                }
113
114
                return $processor;
115
            }
116
        }
117
118
        // match was not found
119
        return false;
120
    }
121
122
    /**
123
     * Method searches dynamic route processor
124
     *
125
     * @param array $processors
126
     *            Callable router's processor
127
     * @param string $route
128
     *            Route
129
     * @return string|bool Result of the router'scall or false if any error occured
130
     */
131
    public function findDynamicRouteProcessor(array &$processors, string $route)
132
    {
133
        $processor = $this->getDynamicRouteProcessor($processors, $route);
134
135
        if ($processor !== false) {
136
            return call_user_func($processor, $route, $this->parameters);
0 ignored issues
show
Bug introduced by
It seems like $processor can also be of type true; however, parameter $function of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

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

136
            return call_user_func(/** @scrutinizer ignore-type */ $processor, $route, $this->parameters);
Loading history...
137
        }
138
139
        return false;
140
    }
141
142
    /**
143
     * Checking that method exists
144
     *
145
     * @param mixed $processor
146
     *            callback object
147
     * @param ?string $functionName
148
     *            callback method
149
     * @return bool true if method does not exists
150
     */
151
    private function methodDoesNotExists($processor, ?string $functionName): bool
152
    {
153
        return isset($processor[0]) && method_exists($processor[0], $functionName) === false;
154
    }
155
156
    /**
157
     * Checking that handler can be called
158
     *
159
     * @param object|array|callable $processor
160
     *            callback object
161
     * @param ?string $functionName
162
     *            callback method
163
     * @return bool
164
     */
165
    private function canBeCalled($processor, ?string $functionName): bool
166
    {
167
        return is_callable($processor) &&
168
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName));
169
    }
170
171
    /**
172
     * Checking that processor can be called as function
173
     *
174
     * @param mixed $processor
175
     *            route processor
176
     * @return bool true if the $processor can be called as function
177
     */
178
    private function isFunction($processor): bool
179
    {
180
        return is_callable($processor) && is_array($processor) === false;
181
    }
182
183
    /**
184
     * Method returns either universal hanler if it fits or normal handler
185
     *
186
     * @param array $processors
187
     *            list of routes and handlers
188
     * @param string $route
189
     *            calling route
190
     * @return mixed processor
191
     */
192
    protected function getExactRouteHandlerOrUniversal(&$processors, string $route)
193
    {
194
        if ($this->universalRouteWasAdded) {
195
            $allRoutes = array_keys($processors);
196
197
            if (array_search('*', $allRoutes) <= array_search($route, $allRoutes)) {
198
                $processor = $processors['*'];
199
            } else {
200
                $processor = $processors[$route];
201
            }
202
        } else {
203
            $processor = $processors[$route];
204
        }
205
206
        return $processor;
207
    }
208
209
    /**
210
     * Method executes route handler
211
     *
212
     * @param mixed $processor
213
     * @param string $route
214
     * @return mixed route handler execution result
215
     */
216
    protected function executeHandler($processor, string $route)
217
    {
218
        if ($this->isFunction($processor)) {
219
            return $processor($route, []);
220
        }
221
222
        $functionName = $processor[1] ?? null;
223
224
        if ($this->canBeCalled($processor, $functionName)) {
225
            // passing route path and parameters
226
            return call_user_func($processor, $route, []);
227
        } else {
228
            $callableDescription = \Mezon\Router\Utils::getCallableDescription($processor);
229
230
            if ($this->methodDoesNotExists($processor, $functionName)) {
231
                throw (new \Exception("'$callableDescription' does not exists"));
232
            } else {
233
                throw (new \Exception("'$callableDescription' must be callable entity"));
234
            }
235
        }
236
    }
237
238
    /**
239
     * Method returns route handler
240
     *
241
     * @param mixed $processors
242
     *            Callable router's processor
243
     * @param string $route
244
     *            Route
245
     * @return array|callable|bool route handler
246
     */
247
    protected function getStaticRouteProcessor(&$processors, string $route)
248
    {
249
        if (isset($processors[$route])) {
250
            $processor = $this->getExactRouteHandlerOrUniversal($processors, $route);
251
        } elseif (isset($processors['*'])) {
252
            $processor = $processors['*'];
253
        } else {
254
            return false;
255
        }
256
257
        return $processor;
258
    }
259
260
    /**
261
     * Method searches route processor
262
     *
263
     * @param mixed $processors
264
     *            Callable router's processor
265
     * @param string $route
266
     *            Route
267
     * @return mixed Result of the router processor
268
     */
269
    public function findStaticRouteProcessor(&$processors, string $route)
270
    {
271
        $processor = $this->getStaticRouteProcessor($processors, $route);
272
273
        if ($processor === false) {
274
            return false;
275
        }
276
277
        return $this->executeHandler($processor, $route);
278
    }
279
280
    /**
281
     * Method returns route parameter
282
     *
283
     * @param string $name
284
     *            Route parameter
285
     * @return string Route parameter
286
     */
287
    public function getParam(string $name): string
288
    {
289
        if (isset($this->parameters[$name]) === false) {
290
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
291
        }
292
293
        return $this->parameters[$name];
294
    }
295
296
    /**
297
     * Does parameter exists
298
     *
299
     * @param string $name
300
     *            Param name
301
     * @return bool True if the parameter exists
302
     */
303
    public function hasParam(string $name): bool
304
    {
305
        return isset($this->parameters[$name]);
306
    }
307
308
    /**
309
     * Getting route by name
310
     *
311
     * @param string $routeName
312
     *            route's name
313
     * @return string route
314
     */
315
    public abstract function getRouteByName(string $routeName): string;
316
317
    /**
318
     * Compiling route into URL
319
     *
320
     * @param string $routeName
321
     *            route name
322
     * @param array $parameters
323
     *            parameters to use in URL
324
     * @return string compiled route
325
     */
326
    public function reverse(string $routeName, array $parameters = []): string
327
    {
328
        $route = $this->getRouteByName($routeName);
329
330
        foreach ($parameters as $name => $value) {
331
            $route = preg_replace('/\[([A-Za-z_\-])\:' . $name . ']/', $value, $route);
332
        }
333
334
        return $route;
335
    }
336
}
337