Completed
Push — master ( d34cec...faecd8 )
by Augusto
02:22
created

Router::__call()   C

Complexity

Conditions 13
Paths 11

Size

Total Lines 75
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 13.2658

Importance

Changes 7
Bugs 0 Features 1
Metric Value
c 7
b 0
f 1
dl 0
loc 75
ccs 38
cts 43
cp 0.8837
rs 5.3314
cc 13
eloc 42
nc 11
nop 2
crap 13.2658

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * This file is part of the Respect\Rest package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Respect\Rest;
10
11
use Exception;
12
use ReflectionClass;
13
use InvalidArgumentException;
14
use Respect\Rest\Routes\AbstractRoute;
15
16
/**
17
 * A router that contains many instances of routes.
18
 *
19
 * @method \Respect\Rest\Routes\AbstractRoute get(\string $path, $routeTarget)
20
 * @method \Respect\Rest\Routes\AbstractRoute post(\string $path, $routeTarget)
21
 * @method \Respect\Rest\Routes\AbstractRoute put(\string $path, $routeTarget)
22
 * @method \Respect\Rest\Routes\AbstractRoute delete(\string $path, $routeTarget)
23
 * @method \Respect\Rest\Routes\AbstractRoute head(\string $path, $routeTarget)
24
 * @method \Respect\Rest\Routes\AbstractRoute options(\string $path, $routeTarget)
25
 * @method \Respect\Rest\Routes\AbstractRoute any(\string $path, $routeTarget)
26
 */
27
class Router
28
{
29
    /**
30
     * @var bool true if this router dispatches itself when destroyed, false
31
     * otherwise
32
     */
33
    public $isAutoDispatched = true;
34
35
    /**
36
     * @var bool true if this router accepts _method HTTP hacks for PUT and
37
     * DELETE via POST
38
     */
39
    public $methodOverriding = false;
40
41
    /**
42
     * @var array An array of routines that must be applied to every route
43
     * instance
44
     */
45
    protected $globalRoutines = array();
46
47
    /**
48
     * @var array An array of main routes for this router
49
     */
50
    protected $routes = array();
51
52
    /**
53
     * @var array An array of side routes (errors, exceptions, etc) for this
54
     * router
55
     */
56
    protected $sideRoutes = array();
57
58
    /**
59
     * @var string The prefix for every requested URI starting with a slash
60
     */
61
    protected $virtualHost = '';
62
63
    /**
64
     * Compares two patterns and returns the first one according to
65
     * similarity or ocurrences of a subpattern
66
     *
67
     * @param string $patternA some pattern
68
     * @param string $patternB some pattern
69
     * @param string $sub      pattern needle
70
     *
71
     * @return bool true if $patternA is before $patternB
72
     */
73 6
    public static function compareOcurrences($patternA, $patternB, $sub)
74
    {
75 6
        return substr_count($patternA, $sub)
76 6
            < substr_count($patternB, $sub);
77
    }
78
79
    /**
80
     * Compares two patterns and returns the first one according to
81
     * similarity or presence of catch-all pattern
82
     *
83
     * @param string $patternA some pattern
84
     * @param string $patternB some pattern
85
     *
86
     * @return bool true if $patternA is before $patternB
87
     */
88 21
    public static function comparePatternSimilarity($patternA, $patternB)
89
    {
90 21
        return 0 === stripos($patternA, $patternB)
91 21
            || $patternA === AbstractRoute::CATCHALL_IDENTIFIER;
92
    }
93
94
    /**
95
     * Compares two patterns and returns the first one according to
96
     * similarity, patterns or ocurrences of a subpattern
97
     *
98
     * @param string $patternA some pattern
99
     * @param string $patternB some pattern
100
     * @param string $sub      pattern needle
101
     *
102
     * @return bool true if $patternA is before $patternB
103
     */
104 21
    public static function compareRoutePatterns($patternA, $patternB, $sub)
105
    {
106 21
        return static::comparePatternSimilarity($patternA, $patternB)
107 21
            || static::compareOcurrences($patternA, $patternB, $sub);
108
    }
109
110
    /**
111
     * Cleans up an return an array of extracted parameters
112
     *
113
     * @param array $params an array of params
114
     *
115
     * @see Respect\Rest\Request::$params
116
     *
117
     * @return array only the non-empty params
118
     */
119 117
    protected static function cleanUpParams(array $params)
120
    {
121
        //using array_values to reset array keys
122 117
        return array_values(
123 117
            array_filter(
124 117
                $params,
125
                function ($param) {
126
127
                    //remove any empty string param
128 69
                    return $param !== '';
129
                }
130 117
            )
131 117
        );
132
    }
133
134
    /**
135
     * Builds and appends many kinds of routes magically.
136
     *
137
     * @param string $method The HTTP method for the new route
138
     */
139 113
    public function __call($method, $args)
140
    {
141 113
        if (count($args) < 2) {
142 5
            throw new InvalidArgumentException(
143
                'Any route binding must at least 2 arguments'
144 5
            );
145
        }
146
147 108
        list($path, $routeTarget) = $args;
148
149
         // Support multiple route definitions as array of paths
150 108
        if (is_array($path)) {
151
            $lastPath = array_pop($path);
152
            foreach ($path as $p) {
153
                $this->$method($p, $routeTarget);
154
            }
155
156
            return $this->$method($lastPath, $routeTarget);
157
        }
158
159
        //closures, func names, callbacks
160 108
        if (is_callable($routeTarget)) {
161
            //raw callback
162 69
            if (!isset($args[2])) {
163 67
                return $this->callbackRoute($method, $path, $routeTarget);
164
            } else {
165 2
                return $this->callbackRoute(
166 2
                    $method,
167 2
                    $path,
168 2
                    $routeTarget,
169 2
                    $args[2]
170 2
                );
171
            }
172
173
        //direct instances
174 40
        } elseif ($routeTarget instanceof Routable) {
175 3
            return $this->instanceRoute($method, $path, $routeTarget);
176
177
        //static returns the argument itself
178 37
        } elseif (!is_string($routeTarget)) {
179 3
            return $this->staticRoute($method, $path, $routeTarget);
180
181
        //static returns the argument itself
182
        } elseif (
183 34
            is_string($routeTarget)
184 34
            && !(class_exists($routeTarget) || interface_exists($routeTarget))
185 34
        ) {
186 20
            return $this->staticRoute($method, $path, $routeTarget);
187
188
        //classes
189
        } else {
190
            //raw classnames
191 14
            if (!isset($args[2])) {
192 7
                return $this->classRoute($method, $path, $routeTarget);
193
194
             //classnames as factories
195 7
            } elseif (is_callable($args[2])) {
196 3
                return $this->factoryRoute(
197 3
                    $method,
198 3
                    $path,
199 3
                    $routeTarget,
200 3
                    $args[2]
201 3
                );
202
203
            //classnames with constructor arguments
204
            } else {
205 4
                return $this->classRoute(
206 4
                    $method,
207 4
                    $path,
208 4
                    $routeTarget,
209 4
                    $args[2]
210 4
                );
211
            }
212
        }
213
    }
214
215
    /**
216
     * @param mixed $virtualHost null for no virtual host or a string prefix
217
     *                           for every URI
218
     */
219 148
    public function __construct($virtualHost = null)
220
    {
221 148
        $this->virtualHost = $virtualHost;
222 148
    }
223
224
    /** If $this->autoDispatched, dispatches the app */
225 50
    public function __destruct()
0 ignored issues
show
Coding Style introduced by
__destruct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
226
    {
227 50
        if (!$this->isAutoDispatched || !isset($_SERVER['SERVER_PROTOCOL'])) {
228 39
            return;
229
        }
230
231 11
        echo $this->run();
232 11
    }
233
234
    /** Runs the router and returns its output */
235
    public function __toString()
236
    {
237
        $string = '';
238
        try {
239
            $string = (string) $this->run();
240
        } catch (\Exception $exception) {
241
            trigger_error($exception->getMessage(), E_USER_ERROR);
242
        }
243
244
        return $string;
245
    }
246
247
    /**
248
     * Applies a routine to every route
249
     *
250
     * @param string $routineName a name of some routine (Accept, When, etc)
251
     * @param array  $param1      some param
252
     * @param array  $param2      some param
253
     * @param array  $etc         This function accepts infinite params
254
     *                            that will be passed to the routine instance
255
     *
256
     * @see Respect\Rest\Request::$params
257
     *
258
     * @return Router the router itself.
259
     */
260 3
    public function always($routineName, $param1 = null, $param2 = null, $etc = null)
0 ignored issues
show
Unused Code introduced by
The parameter $routineName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $param1 is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $param2 is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $etc is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
261
    {
262 3
        $params                 = func_get_args();
263 3
        $routineName            = array_shift($params);
264 3
        $routineClassName       = 'Respect\\Rest\\Routines\\'.$routineName;
265 3
        $routineClass           = new ReflectionClass($routineClassName);
266 3
        $routineInstance        = $routineClass->newInstanceArgs($params);
267 3
        $this->globalRoutines[] = $routineInstance;
268
269 3
        foreach ($this->routes as $route) {
270 1
            $route->appendRoutine($routineInstance);
271 3
        }
272
273 3
        return $this;
274
    }
275
276
    /**
277
     * Appends a pre-built route to the dispatcher
278
     *
279
     * @param AbstractRoute $route Any route
280
     *
281
     * @return Router the router itself
282
     */
283 141
    public function appendRoute(AbstractRoute $route)
284
    {
285 141
        $this->routes[]     = $route;
286 141
        $route->sideRoutes  = &$this->sideRoutes;
287 141
        $route->virtualHost = $this->virtualHost;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->virtualHost of type string is incompatible with the declared type array of property $virtualHost.

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...
288
289 141
        foreach ($this->globalRoutines as $routine) {
290 1
            $route->appendRoutine($routine);
291 141
        }
292
293 141
        return $this;
294
    }
295
296
    /**
297
     * Appends a pre-built side route to the dispatcher
298
     *
299
     * @param AbstractRoute $route Any route
300
     *
301
     * @return Router the router itself
302
     */
303
    public function appendSideRoute(AbstractRoute $route)
304
    {
305
        $this->sideRoutes[] = $route;
306
307
        foreach ($this->globalRoutines as $routine) {
308
            $route->appendRoutine($routine);
309
        }
310
311
        return $this;
312
    }
313
314
    /**
315
     * Creates and returns a callback-based route
316
     *
317
     * @param string   $method    The HTTP method
318
     * @param string   $path      The URI pattern for this route
319
     * @param callable $callback  Any callback for this route
320
     * @param array    $arguments Additional arguments for the callback
321
     *
322
     * @return Respect\Rest\Routes\Callback The route instance
323
     */
324 96
    public function callbackRoute(
325
        $method,
326
        $path,
327
        $callback,
328
        array $arguments = array()
329
    ) {
330 96
        $route = new Routes\Callback($method, $path, $callback, $arguments);
331 96
        $this->appendRoute($route);
332
333 96
        return $route;
334
    }
335
336
    /**
337
     * Creates and returns a class-based route
338
     *
339
     * @param string $method    The HTTP method
340
     * @param string $path      The URI pattern for this route
341
     * @param string $class     Some class name
342
     * @param array  $arguments The class constructor arguments
343
     *
344
     * @return Respect\Rest\Routes\ClassName The route instance
345
     */
346 12
    public function classRoute($method, $path, $class, array $arguments = array())
347
    {
348 12
        $route = new Routes\ClassName($method, $path, $class, $arguments);
349 12
        $this->appendRoute($route);
350
351 12
        return $route;
352
    }
353
354
    /**
355
     * Dispatches the router
356
     *
357
     * @param mixed $method null to infer it or an HTTP method (GET, POST, etc)
358
     * @param mixed $uri    null to infer it or a request URI path (/foo/bar)
359
     *
360
     * @return mixed Whatever you returned from your model
361
     */
362 98
    public function dispatch($method = null, $uri = null)
363
    {
364 98
        return $this->dispatchRequest(new Request($method, $uri));
365
    }
366
367
    /**
368
     * Dispatch the current route with a custom Request
369
     *
370
     * @param Request $request Some request
371
     *
372
     * @return mixed Whatever the dispatched route returns
373
     */
374 134
    public function dispatchRequest(Request $request = null)
375
    {
376 134
        if ($this->isRoutelessDispatch($request)) {
377 2
            return $this->request;
0 ignored issues
show
Bug introduced by
The property request does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
378
        }
379
380 132
        return $this->routeDispatch();
381
    }
382
383
    /**
384
     * Creates and returns a side-route for catching exceptions
385
     *
386
     * @param string $className The name of the exception class you want to
387
     *                          catch. 'Exception' will catch them all.
388
     * @param string $callback  The function to run when an exception is cautght
389
     *
390
     * @return Respect\Rest\Routes\Exception
391
     */
392 1
    public function exceptionRoute($className, $callback = null)
393
    {
394 1
        $route = new Routes\Exception($className, $callback);
395 1
        $this->appendSideRoute($route);
396
397 1
        return $route;
398
    }
399
400
    /**
401
     * Creates and returns a side-route for catching errors
402
     *
403
     * @param string $callback The function to run when an error is cautght
404
     *
405
     * @return Respect\Rest\Routes\Error
406
     */
407 1
    public function errorRoute($callback)
408
    {
409 1
        $route = new Routes\Error($callback);
410 1
        $this->appendSideRoute($route);
411
412 1
        return $route;
413
    }
414
415
    /**
416
     * Creates and returns an factory-based route
417
     *
418
     * @param string $method    The HTTP metod (GET, POST, etc)
419
     * @param string $path      The URI Path (/foo/bar...)
420
     * @param string $className The class name of the factored instance
421
     * @param string $factory   Any callable
422
     *
423
     * @return Respect\Rest\Routes\Factory The route created
424
     */
425 3
    public function factoryRoute($method, $path, $className, $factory)
426
    {
427 3
        $route = new Routes\Factory($method, $path, $className, $factory);
428 3
        $this->appendRoute($route);
429
430 3
        return $route;
431
    }
432
433
    /**
434
     * Iterates over a list of routes and return the allowed methods for them
435
     *
436
     * @param array $routes an array of AbstractRoute
437
     *
438
     * @return array an array of unique allowed methods
439
     */
440 134
    public function getAllowedMethods(array $routes)
441
    {
442 134
        $allowedMethods = array();
443
444 134
        foreach ($routes as $route) {
445 127
            $allowedMethods[] = $route->method;
446 134
        }
447
448 134
        return array_unique($allowedMethods);
449
    }
450
451
    /**
452
     * Checks if router overrides the method with _method hack
453
     *
454
     * @return bool true if the router overrides current request method, false
455
     *              otherwise
456
     */
457 134
    public function hasDispatchedOverridenMethod()
0 ignored issues
show
Coding Style introduced by
hasDispatchedOverridenMethod uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
458
    {
459 134
        return $this->request                    //Has dispatched
460 134
            && $this->methodOverriding           //Has method overriting
461 134
            && isset($_REQUEST['_method'])       //Has a hacky parameter
462 134
            && $this->request->method == 'POST'; //Only post is allowed for this
463
    }
464
465
    /**
466
     * Creates and returns an instance-based route
467
     *
468
     * @param string $method  The HTTP metod (GET, POST, etc)
469
     * @param string $path    The URI Path (/foo/bar...)
470
     * @param string $intance An instance of Routinable
0 ignored issues
show
Documentation introduced by
There is no parameter named $intance. Did you maybe mean $instance?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
471
     *
472
     * @return Respect\Rest\Routes\Instance The route created
473
     */
474 8
    public function instanceRoute($method, $path, $instance)
475
    {
476 8
        $route = new Routes\Instance($method, $path, $instance);
477 8
        $this->appendRoute($route);
478
479 8
        return $route;
480
    }
481
482
    /**
483
     * Checks if request is a global OPTIONS (OPTIONS * HTTP/1.1)
484
     *
485
     * @return bool true if the request is a global options, false otherwise
486
     */
487 134
    public function isDispatchedToGlobalOptionsMethod()
488
    {
489 134
        return $this->request->method === 'OPTIONS'
490 134
            && $this->request->uri === '*';
491
    }
492
493
    /**
494
     * Checks if a request doesn't apply for routes at all
495
     *
496
     * @param Request $request A request
497
     *
498
     * @return bool true if the request doesn't apply for routes
499
     */
500 134
    public function isRoutelessDispatch(Request $request = null)
0 ignored issues
show
Coding Style introduced by
isRoutelessDispatch uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
501
    {
502 134
        $this->isAutoDispatched = false;
503
504 134
        if (!$request) {
505 13
            $request = new Request();
506 13
        }
507
508 134
        $this->request = $request;
509
510 134
        if ($this->hasDispatchedOverridenMethod()) {
511 2
            $request->method = strtoupper($_REQUEST['_method']);
512 2
        }
513
514 134
        if ($this->isDispatchedToGlobalOptionsMethod()) {
515 2
            $allowedMethods = $this->getAllowedMethods($this->routes);
516
517 2
            if ($allowedMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allowedMethods of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
518 2
                header('Allow: '.implode(', ', array_unique($allowedMethods)));
519 2
            }
520
521 2
            return true;
522
        }
523 132
    }
524
525
    /**
526
     * Performs the main route dispatching mechanism
527
     */
528 132
    public function routeDispatch()
529
    {
530 132
        $this->applyVirtualHost();
531 132
        $this->sortRoutesByComplexity();
532
533 132
        $matchedByPath  = $this->getMatchedRoutesByPath();
534 132
        $allowedMethods = $this->getAllowedMethods(
535 132
            iterator_to_array($matchedByPath)
536 132
        );
537
538
        //OPTIONS? Let's inform the allowd methods
539 132
        if ($this->request->method === 'OPTIONS' && $allowedMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allowedMethods of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
540 3
            $this->handleOptionsRequest($allowedMethods, $matchedByPath);
541 132
        } elseif (0 === count($matchedByPath)) {
542 7
            header('HTTP/1.1 404');
543 129
        } elseif (!$this->routineMatch($matchedByPath) instanceof Request) {
544 6
            $this->informMethodNotAllowed($allowedMethods);
545 6
        }
546
547 132
        return $this->request;
548
    }
549
550
    /**
551
     * Dispatches and get response with default request parameters
552
     *
553
     * @param Request $request Some request
554
     *
555
     * @return string the response string
556
     */
557 18
    public function run(Request $request = null)
558
    {
559 18
        $route = $this->dispatchRequest($request);
560
        if (
561
            !$route
562 18
            || (isset($request->method)
563 18
                && $request->method === 'HEAD')
564 18
        ) {
565
            return;
566
        }
567
568 18
        $response = $route->response();
569
570 16
        if (is_resource($response)) {
571 1
            fpassthru($response);
572
573 1
            return '';
574
        }
575
576 15
        return (string) $response;
577
    }
578
579
    /**
580
     * Creates and returns a static route
581
     *
582
     * @param string $method      The HTTP metod (GET, POST, etc)
583
     * @param string $path        The URI Path (/foo/bar...)
584
     * @param string $staticValue Some static value to be printed
585
     *
586
     * @return Respect\Rest\Routes\StaticValue The route created
587
     */
588 23
    public function staticRoute($method, $path, $staticValue)
589
    {
590 23
        $route = new Routes\StaticValue($method, $path, $staticValue);
591 23
        $this->appendRoute($route);
592
593 23
        return $route;
594
    }
595
596
    /** Appliesthe virtualHost prefix on the current request */
597 132
    protected function applyVirtualHost()
598
    {
599 132
        if ($this->virtualHost) {
600 5
            $this->request->uri = preg_replace(
601 5
                '#^'.preg_quote($this->virtualHost).'#',
602 5
                '',
603 5
                $this->request->uri
604 5
            );
605 5
        }
606 132
    }
607
608
    /**
609
     * Configures a request for a specific route with specific parameters
610
     *
611
     * @param Request       $request Some request
612
     * @param AbstractRoute $route   Some route
613
     * @param array         $param   A list of URI params
0 ignored issues
show
Documentation introduced by
There is no parameter named $param. Did you maybe mean $params?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
614
     *
615
     * @see Respect\Rest\Request::$params
616
     *
617
     * @return Request a configured Request instance
618
     */
619 117
    protected function configureRequest(
620
        Request $request,
621
        AbstractRoute $route,
622
        array $params = array()
623
    ) {
624 117
        $request->route = $route;
625 117
        $request->params = $params;
626
627 117
        return $request;
628
    }
629
630
    /**
631
     * Return routes matched by path
632
     *
633
     * @return SplObjectStorage a list of routes matched by path
634
     */
635 132
    protected function getMatchedRoutesByPath()
636
    {
637 132
        $matched = new \SplObjectStorage();
638
639 132
        foreach ($this->routes as $route) {
640 129
            if ($this->matchRoute($this->request, $route, $params)) {
641 125
                $matched[$route] = $params;
642 125
            }
643 132
        }
644
645 132
        return $matched;
646
    }
647
648
    /**
649
     * Sends an Allow header with allowed methods from a list
650
     *
651
     * @param array $allowedMehods A list of allowed methods
0 ignored issues
show
Documentation introduced by
There is no parameter named $allowedMehods. Did you maybe mean $allowedMethods?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
652
     *
653
     * @return null sends an Allow header.
654
     */
655 9
    protected function informAllowedMethods(array $allowedMethods)
656
    {
657 9
        header('Allow: '.implode(', ', $allowedMethods));
658 9
    }
659
660
    /**
661
     * Informs the PHP environment of a not allowed method alongside
662
     * its allowed methods for that path
663
     *
664
     * @param array $allowedMehods A list of allowed methods
0 ignored issues
show
Documentation introduced by
There is no parameter named $allowedMehods. Did you maybe mean $allowedMethods?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
665
     *
666
     * @return null sends HTTP Status Line and Allow header.
667
     */
668 6
    protected function informMethodNotAllowed(array $allowedMethods)
669
    {
670 6
        header('HTTP/1.1 405');
671
672 6
        if (!$allowedMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allowedMethods of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
673
            return;
674
        }
675
676 6
        $this->informAllowedMethods($allowedMethods);
677 6
        $this->request->route = null;
678 6
    }
679
680
    /**
681
     * Handles a OPTIONS request, inform of the allowed methods and
682
     * calls custom OPTIONS handler (if any).
683
     *
684
     * @param array $allowedMehods A list of allowed methods
0 ignored issues
show
Documentation introduced by
There is no parameter named $allowedMehods. Did you maybe mean $allowedMethods?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
685
     * @param \SplObjectStorage $matchedByPath A list of matched routes by path
686
     *
687
     * @return null sends Allow header.
688
     */
689 3
    protected function handleOptionsRequest(array $allowedMethods, \SplObjectStorage $matchedByPath)
690
    {
691 3
        $this->informAllowedMethods($allowedMethods);
692
693 3
        if (in_array('OPTIONS', $allowedMethods)) {
694 1
            $this->routineMatch($matchedByPath);
695 1
        } else {
696 2
            $this->request->route = null;
697
        }
698 3
    }
699
700
    /**
701
     * Checks if a route matches a method
702
     *
703
     * @param AbstractRoute $route      A route instance
704
     * @param string        $methodName Name of the method to match
705
     *
706
     * @return bool true if route matches
707
     */
708 123
    protected function matchesMethod(AbstractRoute $route, $methodName)
709
    {
710 123
        return 0 !== stripos($methodName, '__')
711 123
            && ($route->method === $this->request->method
712 122
                || $route->method === 'ANY'
713 31
                || ($route->method === 'GET'
714 7
                    && $this->request->method === 'HEAD'
715 5
                )
716 123
            );
717
    }
718
719
    /**
720
     * Returns true if the passed route matches the passed request
721
     *
722
     * @param Request       $request Some request
723
     * @param AbstractRoute $route   Some route
724
     * @param array         $params  A list of URI params
725
     *
726
     * @see Respect\Rest\Request::$params
727
     *
728
     * @return bool true if the route matches the request with that params
729
     */
730 129
    protected function matchRoute(
731
        Request $request,
732
        AbstractRoute $route,
733
        &$params = array()
734
    ) {
735 129
        if ($route->match($request, $params)) {
736 125
            $request->route = $route;
737
738 125
            return true;
739
        }
740 12
    }
741
742
    /**
743
     * Checks if a route matches its routines
744
     *
745
     * @param SplObjectStorage $matchedByPath A list of routes matched by path
746
     *
747
     * @return bool true if route matches its routines
748
     */
749 123
    protected function routineMatch(\SplObjectStorage $matchedByPath)
750
    {
751 123
        $badRequest = false;
752
753 123
        foreach ($matchedByPath as $route) {
754 123
            if ($this->matchesMethod($route, $this->request->method)) {
755 119
                $tempParams = $matchedByPath[$route];
756 119
                if ($route->matchRoutines($this->request, $tempParams)) {
757 117
                    return $this->configureRequest(
758 117
                        $this->request,
759 117
                        $route,
760 117
                        static::cleanUpParams($tempParams)
761 117
                    );
762
                } else {
763 2
                    $badRequest = true;
764
                }
765 2
            }
766 8
        }
767
768 6
        return $badRequest ? false : null;
769
    }
770
771
    /** Sorts current routes according to path and parameters */
772 132
    protected function sortRoutesByComplexity()
773
    {
774 132
        usort(
775 132
            $this->routes,
776 21
            function ($a, $b) {
777 21
                $a = $a->pattern;
778 21
                $b = $b->pattern;
779 21
                $pi = AbstractRoute::PARAM_IDENTIFIER;
780
781
                //Compare similarity and ocurrences of "/"
782 21
                if (Router::compareRoutePatterns($a, $b, '/')) {
783 18
                    return 1;
784
785
                //Compare similarity and ocurrences of /*
786 5
                } elseif (Router::compareRoutePatterns($a, $b, $pi)) {
787 2
                    return -1;
788
789
                //Hard fallback for consistency
790
                } else {
791 5
                    return 1;
792
                }
793
            }
794 132
        );
795 132
    }
796
}
797