Route::createInstances()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 17
rs 8.8333
1
<?php
2
/**
3
 * Klein (klein.php) - A fast & flexible router for PHP
4
 *
5
 * @author      Chris O'Hara <[email protected]>
6
 * @author      Trevor Suarez (Rican7) (contributor and v2 refactorer)
7
 * @copyright   (c) Chris O'Hara
8
 * @link        https://github.com/klein/klein.php
9
 * @license     MIT
10
 */
11
12
namespace app\framework\Component\Routing;
13
14
use app\framework\Component\Config\Config;
15
use app\framework\Component\Http\MiddlewareInterface;
16
use app\framework\Component\Routing\Exceptions\MiddlewareNotFoundException;
17
use InvalidArgumentException;
18
19
/**
20
 * Route
21
 *
22
 * Class to represent a route definition
23
 * @package app\framework\Component\Routing
24
 */
25
class Route
26
{
27
    /**
28
     * Properties
29
     */
30
31
    /**
32
     * The callback method to execute when the route is matched
33
     *
34
     * Any valid "callable" type is allowed
35
     *
36
     * @link http://php.net/manual/en/language.types.callable.php
37
     * @type callable
38
     */
39
    protected $callback;
40
41
    /**
42
     * The URL path to match
43
     *
44
     * Allows for regular expression matching and/or basic string matching
45
     *
46
     * Examples:
47
     * - '/posts'
48
     * - '/posts/[:post_slug]'
49
     * - '/posts/[i:id]'
50
     *
51
     * @type string
52
     */
53
    protected $path;
54
55
    /**
56
     * The HTTP method to match
57
     *
58
     * May either be represented as a string or an array containing multiple methods to match
59
     *
60
     * Examples:
61
     * - 'POST'
62
     * - array('GET', 'POST')
63
     *
64
     * @type string|array
65
     */
66
    protected $method;
67
68
    /**
69
     * Whether or not to count this route as a match when counting total matches
70
     *
71
     * @type boolean
72
     */
73
    protected $count_match;
74
75
    /**
76
     * The name of the route
77
     *
78
     * Mostly used for reverse routing
79
     *
80
     * @type string
81
     */
82
    protected $name;
83
84
    /**
85
     *
86
     *
87
     * @var array
88
     */
89
    protected $middleware = [];
90
91
    /**
92
     * Methods
93
     */
94
95
    /**
96
     * Constructor
97
     *
98
     * @param callable $callback
99
     * @param string $path
100
     * @param string|array $method
101
     * @param boolean $count_match
102
     * @param null $name
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $name is correct as it would always require null to be passed?
Loading history...
103
     */
104
    public function __construct($callback, $path = null, $method = null, $count_match = true, $name = null)
105
    {
106
        // Initialize some properties (use our setters so we can validate param types)
107
        $this->setCallback($callback);
108
        $this->setPath($path);
109
        $this->setMethod($method);
110
        $this->setCountMatch($count_match);
111
        $this->setName($name);
112
    }
113
114
    /**
115
     * Get the callback
116
     *
117
     * @return callable
118
     */
119
    public function getCallback()
120
    {
121
        return $this->callback;
122
    }
123
124
    /**
125
     * Set the callback
126
     *
127
     * @param callable $callback
128
     * @throws InvalidArgumentException If the callback isn't a callable
129
     * @return Route
130
     */
131
    public function setCallback($callback)
132
    {
133
        if (!is_string($callback)) {
134
            if (!is_callable($callback)) {
135
                throw new InvalidArgumentException(
136
                    'Expected a callable or string. Got an uncallable or not string'. gettype($callback)
137
                );
138
            }
139
        }
140
141
        $this->callback = $callback;
142
143
        return $this;
144
    }
145
146
    /**
147
     * Get the path
148
     *
149
     * @return string
150
     */
151
    public function getPath()
152
    {
153
        return $this->path;
154
    }
155
156
    /**
157
     * Set the path
158
     *
159
     * @param string $path
160
     * @return Route
161
     */
162
    public function setPath($path)
163
    {
164
        $this->path = (string) $path;
165
166
        return $this;
167
    }
168
169
    /**
170
     * Get the method
171
     *
172
     * @return string|array
173
     */
174
    public function getMethod()
175
    {
176
        return $this->method;
177
    }
178
179
    /**
180
     * Set the method
181
     *
182
     * @param string|array|null $method
183
     * @throws InvalidArgumentException If a non-string or non-array type is passed
184
     * @return Route
185
     */
186
    public function setMethod($method)
187
    {
188
        // Allow null, otherwise expect an array or a string
189
        if (null !== $method && !is_array($method) && !is_string($method)) {
0 ignored issues
show
introduced by
The condition is_string($method) is always true.
Loading history...
190
            throw new InvalidArgumentException('Expected an array or string. Got a '. gettype($method));
191
        }
192
193
        $this->method = $method;
194
195
        return $this;
196
    }
197
198
    /**
199
     * Get the count_match
200
     *
201
     * @return boolean
202
     */
203
    public function getCountMatch()
204
    {
205
        return $this->count_match;
206
    }
207
208
    /**
209
     * Set the count_match
210
     *
211
     * @param boolean $count_match
212
     * @return Route
213
     */
214
    public function setCountMatch($count_match)
215
    {
216
        $this->count_match = (boolean) $count_match;
217
218
        return $this;
219
    }
220
221
    /**
222
     * Get the name
223
     *
224
     * @return string
225
     */
226
    public function getName()
227
    {
228
        return $this->name;
229
    }
230
231
    /**
232
     * Set the name
233
     *
234
     * @param string $name
235
     * @return Route
236
     */
237
    public function setName($name)
238
    {
239
        if (null !== $name) {
0 ignored issues
show
introduced by
The condition null !== $name is always true.
Loading history...
240
            $this->name = (string) $name;
241
        } else {
242
            $this->name = $name;
243
        }
244
245
        return $this;
246
    }
247
248
    /**
249
     * Getter for middleware
250
     *
251
     * @return array
252
     * @throws MiddlewareNotFoundException
253
     */
254
    public function getMiddleware()
255
    {
256
        $this->createInstances($this->getFromConfig("global"));
257
        $this->createInstances($this->middleware);
258
259
        return $this->middleware;
260
    }
261
262
    /**
263
     * Set middleware attr. and instantiate classes
264
     * to just call the handle method later
265
     *
266
     * @param string $name
267
     */
268
    public function middleware(string $name)
269
    {
270
        $obj = $this->getFromConfig("middleware")[$name];
271
272
        if (is_null($obj)) {
273
            $obj = $this->getFromConfig("groups")[$name];
274
275
            $this->doesMiddlewareExist($obj);
276
        }
277
278
        $this->middleware[$name] = $obj;
279
    }
280
281
    /**
282
     * Little helper to load from middleware config
283
     *
284
     * @param $config
285
     * @return mixed
286
     */
287
    private function getFromConfig($config)
288
    {
289
        return Config::getInstance()->get($config, 'middleware');
290
    }
291
292
    /**
293
     * Create instances from Configured class names <br>
294
     * <code>
295
     * $middlename = [
296
     *      $middlewareName => $fullClassName
297
     * ]
298
     * </code>
299
     * @param array $middleware
300
     * @throws MiddlewareNotFoundException
301
     */
302
    private function createInstances(array $middleware)
303
    {
304
        // to handle the case where no global middleware is configured
305
        if($middleware == []) {
306
            return;
307
        }
308
309
        foreach ($middleware as $name => $class) {
310
            if (is_array($class)) {
311
                foreach ($class as $key => $value) {
312
                    if($this->doesMiddlewareExist($value)) {
313
                        $this->middleware[$name][$key] = new $value;
314
                    }
315
                }
316
            } else {
317
                if($this->doesMiddlewareExist($class)) {
318
                    $this->middleware[$name] = new $class;
319
                }
320
            }
321
        }
322
    }
323
324
    /**
325
     * Checks if the middleware class exists
326
     *
327
     * @param string|MiddlewareInterface $fullyQualifiedClassName
328
     * @return bool
329
     * @throws MiddlewareNotFoundException
330
     */
331
    private function doesMiddlewareExist($fullyQualifiedClassName):bool
332
    {
333
        if(is_array($fullyQualifiedClassName)) {
0 ignored issues
show
introduced by
The condition is_array($fullyQualifiedClassName) is always false.
Loading history...
334
            foreach($fullyQualifiedClassName as $className) {
335
                return $this->checkMiddlewareExists($className);
336
            }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
337
        } else {
338
            return $this->checkMiddlewareExists($fullyQualifiedClassName);
339
        }
340
    }
341
342
    /**
343
     * Actually checking if the middleware class exists
344
     *
345
     * @param $className
346
     * @return bool
347
     * @throws MiddlewareNotFoundException
348
     */
349
    private function checkMiddlewareExists($className)
350
    {
351
        if (class_exists($className)) {
352
            return true;
353
        } else {
354
            // if $fullyQualifiedClassName is object we dont throw an
355
            // exception because middleware was already created.
356
            if (! is_object($className)) {
357
                throw new MiddlewareNotFoundException("Middleware `%s` not found", $className);
358
            }
359
360
            return false;
361
        }
362
    }
363
364
    /**
365
     * Magic "__invoke" method
366
     *
367
     * Allows the ability to arbitrarily call this instance like a function
368
     *
369
     * @param mixed $args Generic arguments, magically accepted
370
     * @return mixed
371
     */
372
    public function __invoke($args = null)
373
    {
374
        $args = func_get_args();
375
376
        return call_user_func_array(
377
            $this->callback,
378
            $args
379
        );
380
    }
381
}
382