Route::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace System;
4
5
class Route
6
{
7
    /**
8
     * Application Object
9
     *
10
     * @var \System\Application
11
     */
12
    private $app;
13
14
    /**
15
     * Routes
16
     *
17
     * @var array
18
     */
19
    private $routes = [];
20
21
    /**
22
     * Current route
23
     *
24
     * @var array
25
     */
26
    public $current = [];
27
28
    /**
29
     * Current page
30
     *
31
     * @var string
32
     */
33
    private $page;
34
35
    /**
36
     * Prefix
37
     *
38
     * @var string
39
     */
40
    private $prefix;
41
42
    /**
43
     * Controller
44
     *
45
     * @var string
46
     */
47
    private $basController;
48
49
    /**
50
     * Middleware
51
     *
52
     * @var string
53
     */
54
    private $middleware = [];
55
56
    /**
57
     * Constructor
58
     *
59
     * @param \System\Application $app
60
     */
61
    public function __construct(Application $app)
62
    {
63
        $this->app = $app;
64
    }
65
66
    /**
67
     * Add route to $routes after processing each parameter
68
     *
69
     * @param string $url
70
     * @param string $action
71
     * @param string|array $requestMethods
72
     * @param string|array $middleware
73
     * @return object $this
74
     */
75
    private function add(string $url, string $action, $requestMethods, $middleware = [])
76
    {
77
        $url = $this->setPrefix($url);
78
        $action = $this->setAction($action);
79
        $middleware = $this->setMiddleware($middleware);
0 ignored issues
show
Bug introduced by
It seems like $middleware defined by $this->setMiddleware($middleware) on line 79 can also be of type array; however, System\Route::setMiddleware() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
80
        $routes = [
81
            'url' => $url,
82
            'pattern' => $this->generatePattern($url),
83
            'action' => $this->getAction($action),
84
            'method' => $requestMethods,
85
            'middleware' => $middleware
86
        ];
87
88
        $this->routes[] = $routes;
89
90
        return $this;
91
    }
92
93
    /**
94
     * Add route GET to $routes
95
     *
96
     * @param string $url
97
     * @param string $action
98
     * @param string|array $middleware
99
     * @return object $this
100
     */
101
    public function get(string $url, string $action, $middleware = [])
102
    {
103
        $this->add($url, $action, 'GET', $middleware);
104
        return $this;
105
    }
106
107
    /**
108
     * Add route POST to $routes
109
     *
110
     * @param string $url
111
     * @param string $action
112
     * @param string|array $middleware
113
     * @return object $this
114
     */
115
    public function post(string $url, string $action, $middleware = [])
116
    {
117
        $this->add($url, $action, 'POST', $middleware);
118
        return $this;
119
    }
120
121
    /**
122
     * Add prefix at the first of $url if exists or not equal to '/'
123
     *
124
     * @param string $url
125
     * @return string $url
126
     */
127
    private function setPrefix($url)
128
    {
129
        if ($this->prefix && $this->prefix !== '/') {
130
            $url = $this->prefix . $url;
131
            $url = rtrim($url, '/');
132
        }
133
        return $url;
134
    }
135
136
    /**
137
     * Add basController at the first of $action if exists
138
     *
139
     * @param string $action
140
     * @return string $action
141
     */
142
    private function setAction($action)
143
    {
144
        if ($this->basController) {
145
            $action = $this->basController . '/' . $action;
146
        }
147
        return $action;
148
    }
149
150
    /**
151
     * Merge the given middleware if exists
152
     *
153
     * @param string $middleware
154
     * @return array
155
     */
156
    private function setMiddleware($middleware)
157
    {
158
        if (!is_array($middleware)) {
159
            $middleware = [$middleware];
160
        }
161
        return array_merge($this->middleware, $middleware);
162
    }
163
164
    /**
165
     * Clean the given path to the right controller and method
166
     *
167
     * @param string $action
168
     * @return array $action
169
     */
170
    private function getAction($action)
171
    {
172
        $action = str_replace('/', '\\', $action);
173
        $action = (strpos($action, '@') != 0) ? $action : $action . '@index';
174
        $action = explode('@', $action);
175
176
        return $action;
177
    }
178
179
    /**
180
     * generate a pattern using regex
181
     *
182
     * @param string $url
183
     * @return string $pattern
184
     */
185
    private function generatePattern($url)
186
    {
187
        $pattern = '#^';
188
        $pattern .= str_replace([':text', ':id'], ['([a-zA-Z0-9-]+)', '(\d+)'], strtolower($url));
189
        $pattern .= '$#';
190
191
        return $pattern;
192
    }
193
194
    /**
195
     * Check if the both methods are true
196
     *
197
     * @param string $pattern
198
     * @param string $methods
199
     * @return bool
200
     */
201
    private function fullMatch($pattern, $methods)
202
    {
203
        return $this->isMatchingPattern($pattern) && $this->app->request->isMatchingRequestMethod($methods);
0 ignored issues
show
Documentation introduced by
The property request does not exist on object<System\Application>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
204
    }
205
206
    /**
207
     * Check if the url of the requesting page is matching the given pattern
208
     *
209
     * @property object $url
210
     * @param string $pattern
211
     * @return bool
212
     */
213
    private function isMatchingPattern($pattern)
214
    {
215
        $url = strtolower($this->app->url->parameters());
0 ignored issues
show
Documentation introduced by
The property url does not exist on object<System\Application>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
216
217
        return preg_match($pattern, $url);
218
    }
219
220
    /**
221
     * Get the rest parameter of the url as paramter for
222
     * the method
223
     *
224
     * @property object $url
225
     * @param string $pattern
226
     * @return bool
227
     */
228
    private function getArgumentsFor($pattern)
229
    {
230
        $url = $this->app->url->parameters();
0 ignored issues
show
Documentation introduced by
The property url does not exist on object<System\Application>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
231
232
        preg_match($pattern, $url, $matches);
233
234
        array_shift($matches);
235
236
        return $matches;
237
    }
238
239
    /**
240
     * Group routes together
241
     *
242
     * @param array $groupOptions
243
     * @param callable $callback
244
     * @return object $this
245
     */
246
    public function group(array $groupOptions, callable $callback)
247
    {
248
        $prefix = $groupOptions['prefix'];
249
        $controller = $groupOptions['controller'];
250
        $middleware = $groupOptions['middleware'];
251
252
        $this->prefix = $prefix;
253
        $this->basController = $controller;
254
        $this->middleware = $middleware;
255
256
        $callback($this);
257
258
        $this->prefix = '';
259
        $this->basController = '';
260
        $this->middleware = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type string of property $middleware.

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...
261
262
        return $this;
263
    }
264
265
    /**
266
     * Set a complate package
267
     *
268
     * @param string $url
269
     * @param string $controller
270
     * @param array $middlewares
271
     * @return object $this
272
     */
273
    public function package(string $url, string $controller, array $middlewares = [])
274
    {
275
        $this->add($url, $controller, 'GET');
276
        $this->add("$url/:id", "$controller@row", ['GET'], $middlewares['row'] ?? []);
277
        $this->add("$url/new", "$controller@new", ['GET'], $middlewares['new'] ?? []);
278
        $this->add("$url/add", "$controller@add", ['POST'], $middlewares['add'] ?? []);
279
        $this->add("$url/update/:id", "$controller@update", ['POST'], $middlewares['update'] ?? []);
280
        $this->add("$url/delete/:id", "$controller@delete", ['POST'], $middlewares['delete'] ?? []);
281
282
        return $this;
283
    }
284
285
    /**
286
     * Loop over the routes and find the right one:
287
     * found: break the loop and check if the request can continue by calling the middleware
288
     *  can be continue: call the right page
289
     *  can't be continue: .....
290
     * not found: call the 404 page
291
     *
292
     * @return string
293
     */
294
    public function getProperRoute()
295
    {
296
        foreach ($this->routes as $route) {
297
            if ($this->fullMatch($route['pattern'], $route['method'])) {
298
                $this->current = $route;
299
300
                $continue = $this->app->request->canRequestContinue($route['middleware']);
0 ignored issues
show
Documentation introduced by
The property request does not exist on object<System\Application>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
301
302
                if ($continue) {
303
                    list($controller, $method) = $route['action'];
304
305
                    $this->page = $controller;
306
307
                    $arguments = $this->getArgumentsFor($route['pattern']);
308
309
                    return (string) $this->app->load->action($controller, $method, $arguments);
0 ignored issues
show
Documentation introduced by
The property load does not exist on object<System\Application>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
310
                }
311
                break;
312
            }
313
        }
314
        $this->page = 'notfound';
315
316
        return notFoundPage();
317
    }
318
319
    /**
320
     * Get current route
321
     *
322
     * @return array $current
323
     */
324
    public function getCurrent()
325
    {
326
        return $this->current;
327
    }
328
329
    /**
330
     * Get current page
331
     *
332
     * @return string $current
333
     */
334
    public function getPage()
335
    {
336
        $page = explode('\\', $this->page);
337
        $length = count($page);
338
339
        return $page[$length - 1];
340
    }
341
}
342