Passed
Push — master ( 05b7e1...957c9d )
by Бабичев
03:54
created

src/Router/Route.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Bavix\Router;
4
5
use Bavix\Slice\Slice;
6
7
class Route implements \Serializable
8
{
9
10
    /**
11
     * @var string
12
     */
13
    protected $defaultRegex = '[\w-А-ЯЁа-яё]+';
14
15
    /**
16
     * @var array
17
     */
18
    protected $http;
19
20
    /**
21
     * @var string
22
     */
23
    protected $path;
24
25
    /**
26
     * @var array
27
     */
28
    protected $regex;
29
30
    /**
31
     * @var string
32
     */
33
    protected $regexPath;
34
35
    /**
36
     * @var string
37
     */
38
    protected $filterPath;
39
40
    /**
41
     * @var array
42
     */
43
    protected $defaults;
44
45
    /**
46
     * @var array
47
     */
48
    protected $methods;
49
50
    /**
51
     * @var array
52
     */
53
    protected $attributes;
54
55
    /**
56
     * @var Slice
57
     */
58
    protected $slice;
59
60
    /**
61
     * Route constructor.
62
     *
63
     * @param array|\Traversable|Slice $data
64
     * @param string                   $defaultRegex
65
     */
66
    public function __construct($data, $defaultRegex = null)
67
    {
68
        $this->slice = Slice::from($data);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Bavix\Slice\Slice::from($data) of type object<self> is incompatible with the declared type object<Bavix\Slice\Slice> of property $slice.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
69
70
        if ($defaultRegex !== null)
71
        {
72
            $this->defaultRegex = $defaultRegex;
73
        }
74
75
        $this->reload();
76
    }
77
78
    /**
79
     * @return array
80
     */
81
    public function getHttp()
82
    {
83
        return $this->http;
84
    }
85
86
    /**
87
     * @return string
88
     */
89
    public function getPath()
90
    {
91
        return $this->path;
92
    }
93
94
    /**
95
     * @return array
96
     */
97
    public function getRegex()
98
    {
99
        return $this->regex;
100
    }
101
102
    /**
103
     * @return string
104
     */
105
    public function getRegexPath()
106
    {
107
        return $this->regexPath;
108
    }
109
110
    /**
111
     * @return string
112
     */
113
    public function getFilterPath()
114
    {
115
        return $this->filterPath;
116
    }
117
118
    /**
119
     * @return array
120
     */
121
    public function getDefaults()
122
    {
123
        return $this->defaults;
124
    }
125
126
    /**
127
     * @return array
128
     */
129
    public function getAttributes()
130
    {
131
        return $this->attributes ?? $this->defaults;
132
    }
133
134
    /**
135
     * @return string
136
     */
137
    protected function pathFilter()
138
    {
139
        return preg_replace_callback(
140
            '~\<(?<key>\w+)(\:(?<value>.+?))?\>~',
141
            function ($matches) {
142
                if (!empty($matches['value']) && empty($this->regex[$matches['key']]))
143
                {
144
                    $this->regex[$matches['key']] = $matches['value'];
145
                }
146
147
                return '<' . $matches['key'] . '>';
148
            },
149
            $this->path
150
        );
151
    }
152
153
    /**
154
     * @param string $path
155
     *
156
     * @return string
157
     */
158
    protected function quote($path)
159
    {
160
        $path = preg_quote($path, '()');
161
        $path = strtr($path, [
162
            '\\(' => '(',
163
            '\\)' => ')',
164
            '\\<' => '<',
165
            '\\>' => '>',
166
        ]);
167
168
        return $this->optional($path);
169
    }
170
171
    /**
172
     * @param string $rulePath
173
     *
174
     * @return string
175
     */
176
    protected function optional($rulePath)
177
    {
178
        return str_replace(')', ')?', $rulePath);
179
    }
180
181
    /**
182
     * @param string $route
183
     *
184
     * @return string
185
     */
186
    protected function toRegex($route)
187
    {
188
        $path = $this->quote($route);
189
190
        return preg_replace_callback(
191
            '~\<(?<key>[\w-]+)\>~',
192
            function ($matches) {
193
                return '(?<' . $matches['key'] . '>' . ($this->regex[$matches['key']] ?? $this->defaultRegex) . ')';
194
            },
195
            $path
196
        );
197
    }
198
199
    /**
200
     * @param string[] $matches
201
     *
202
     * @return array
203
     */
204
    protected function attributes($matches)
205
    {
206
        return array_filter($matches, function ($value, $key) {
207
            return !is_int($key) && (is_numeric($value) || !empty($value));
208
        }, ARRAY_FILTER_USE_BOTH);
209
    }
210
211
    /**
212
     * @param string $method
213
     *
214
     * @return bool
215
     */
216
    public function methodValid($method)
217
    {
218
        return empty($this->methods) ||
219
            in_array($method, $this->methods, true) ||
220
            ($method === 'AJAX' && isAjax());
221
    }
222
223
    /**
224
     * @param string $uri
225
     *
226
     * @return bool
227
     */
228
    public function uriValid($uri)
229
    {
230
        $result = preg_match('~^' . $this->regexPath . '$~u', $uri, $matches);
231
232
        if ($result)
233
        {
234
            $this->attributes = array_merge($this->defaults, $this->attributes($matches));
235
        }
236
237
        return $result !== 0;
238
    }
239
240
    /**
241
     * @param string $uri
242
     * @param string $method
243
     *
244
     * @return bool
245
     */
246
    public function test($uri, $method)
247
    {
248
        if (!$this->methodValid($method))
249
        {
250
            return false;
251
        }
252
253
        return $this->uriValid($uri);
254
    }
255
256
    /**
257
     * @param array  $http
258
     * @param string $path
259
     *
260
     * @return string
261
     */
262
    protected function regexUri(array $http, $path)
263
    {
264
        return $http['protocol'] . '\:\/{2}' . $http['host'] . $path;
265
    }
266
267
    /**
268
     * @return string
269
     */
270
    protected function regex()
271
    {
272
        $this->filterPath = $this->pathFilter();
273
274
        $regex = $this->toRegex($this->filterPath);
275
        $http  = $this->http;
276
277
        if (!$this->http['protocol'])
278
        {
279
            $http['protocol'] = 'https?';
280
        }
281
282
        if (!$this->http['host'])
283
        {
284
            $http['host'] = '[^\/]+';
285
        }
286
287
        return $this->regexUri($http, $regex);
288
    }
289
290
    /**
291
     * reload route
292
     */
293
    protected function reload()
294
    {
295
        $http = [
296
            'protocol' => null,
297
            'host'     => null
298
        ];
299
300
        $this->http      = (array)$this->slice->atData('http', $http);
301
        $this->defaults  = (array)$this->slice->atData('defaults');
302
        $this->methods   = (array)$this->slice->atData('methods');
303
        $this->regex     = (array)$this->slice->atData('regex');
304
        $this->path      = $this->slice->atData('path');
305
        $this->regexPath = $this->regex();
306
    }
307
308
    /**
309
     * @inheritdoc
310
     */
311
    public function serialize()
312
    {
313
        return serialize([
314
            'defaultRegex' => $this->defaultRegex,
315
            'http'         => $this->http,
316
            'path'         => $this->path,
317
            'regex'        => $this->regex,
318
            'regexPath'    => $this->regexPath,
319
            'filterPath'   => $this->filterPath,
320
            'defaults'     => $this->defaults,
321
            'methods'      => $this->methods,
322
            'attributes'   => $this->attributes,
323
            'slice'        => $this->slice,
324
        ]);
325
    }
326
327
    /**
328
     * @inheritdoc
329
     */
330
    public function unserialize($serialized)
331
    {
332
        $data = unserialize($serialized, []);
333
334
        foreach ($data as $variable => $value)
335
        {
336
            $this->{$variable} = $value;
337
        }
338
    }
339
340
341
}
342