Router   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 11
Bugs 2 Features 2
Metric Value
wmc 16
c 11
b 2
f 2
lcom 2
cbo 4
dl 0
loc 204
ccs 60
cts 60
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B cache() 0 34 3
A clearCache() 0 6 1
A getCacheKey() 0 4 1
A routingToController() 0 4 1
A getControllerAction() 0 22 3
A makeControllerActionClosure() 0 6 1
B createRoute() 0 26 3
A newRoute() 0 4 1
A addWhereClausesToRoute() 0 7 1
1
<?php namespace MaartenStaa\Routing;
2
3
/**
4
 * Copyright (c) 2015 by Maarten Staa.
5
 *
6
 * Some rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions are
10
 * met:
11
 *
12
 *     * Redistributions of source code must retain the above copyright
13
 *       notice, this list of conditions and the following disclaimer.
14
 *
15
 *     * Redistributions in binary form must reproduce the above
16
 *       copyright notice, this list of conditions and the following
17
 *       disclaimer in the documentation and/or other materials provided
18
 *       with the distribution.
19
 *
20
 *     * The names of the contributors may not be used to endorse or
21
 *       promote products derived from this software without specific
22
 *       prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
 */
36
37
use Closure;
38
use Illuminate\Container\Container;
39
use Illuminate\Events\Dispatcher;
40
use Illuminate\Routing\Router as LaravelRouter;
41
42
class Router extends LaravelRouter
43
{
44
    /**
45
     * Version of the cache key
46
     *
47
     * @var string
48
     */
49
    protected $cacheVersion = 'v1';
50
51
    /**
52
     * Create a new Router instance.
53
     *
54
     * @param \Illuminate\Events\Dispatcher        $events
55
     * @param \Illuminate\Container\Container|null $container
56
     */
57 44
    public function __construct(Dispatcher $events, Container $container = null)
58
    {
59 44
        parent::__construct($events, $container);
60
61 44
        $this->routes = new RouteCollection;
62 44
    }
63
64
    /**
65
     * Indicate that the routes that are defined in the given callback
66
     * should be cached.
67
     *
68
     * @param  string  $filename
69
     * @param  Closure $callback
70
     * @param  int     $cacheMinutes
71
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
72
     */
73 36
    public function cache($filename, Closure $callback, $cacheMinutes = 1440)
74
    {
75
        // If $cacheMinutes is 0 or lower, there is no need to cache anything.
76 36
        if ($cacheMinutes <= 0) {
77
            // Call closure to define routes that should be cached.
78 4
            call_user_func($callback, $this);
79
80
            // No cache key.
81 4
            return null;
82
        }
83
84 32
        $cacher = $this->container['cache'];
85 32
        $cacheKey = $this->getCacheKey($filename);
86
87
        // Check if the current route group is cached.
88 32
        if (($cache = $cacher->get($cacheKey)) !== null) {
89 28
            $this->routes->restoreRouteCache($cache);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Illuminate\Routing\RouteCollection as the method restoreRouteCache() does only exist in the following sub-classes of Illuminate\Routing\RouteCollection: MaartenStaa\Routing\RouteCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
90 28
        } else {
91
            // Back up current RouteCollection contents.
92 32
            $this->routes->saveRouteCollection();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Illuminate\Routing\RouteCollection as the method saveRouteCollection() does only exist in the following sub-classes of Illuminate\Routing\RouteCollection: MaartenStaa\Routing\RouteCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
93
94
            // Call closure to define routes that should be cached.
95 32
            call_user_func($callback, $this);
96
97
            // Put routes in cache.
98 32
            $cache = $this->routes->getCacheableRoutes();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Illuminate\Routing\RouteCollection as the method getCacheableRoutes() does only exist in the following sub-classes of Illuminate\Routing\RouteCollection: MaartenStaa\Routing\RouteCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
99 32
            $cacher->put($cacheKey, $cache, $cacheMinutes);
100
101
            // And restore the routes that shouldn't be cached.
102 32
            $this->routes->restoreRouteCollection();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Illuminate\Routing\RouteCollection as the method restoreRouteCollection() does only exist in the following sub-classes of Illuminate\Routing\RouteCollection: MaartenStaa\Routing\RouteCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
103
        }
104
105 32
        return $cacheKey;
106
    }
107
108
    /**
109
     * Clear the cached data for the given routes file.
110
     *
111
     * @param string $filename
112
     */
113 4
    public function clearCache($filename)
114
    {
115 4
        $cacher = $this->container['cache'];
116
117 4
        $cacher->forget($this->getCacheKey($filename));
118 4
    }
119
120
    /**
121
     * Get the key under which the routes cache for the given file should be stored.
122
     *
123
     * @param  string $filename
124
     * @return string
125
     */
126 32
    protected function getCacheKey($filename)
127
    {
128 32
        return 'routes.cache.'.$this->cacheVersion.'.'.md5($filename).filemtime($filename);
129
    }
130
131
    /**
132
     * Determine if the action is routing to a controller.
133
     *
134
     * @param  array  $action
135
     * @return bool
136
     */
137 44
    public function routingToController($action)
138
    {
139 44
        return parent::routingToController($action);
140
    }
141
142
    /**
143
     * Add a controller based route action to the action array.
144
     *
145
     * @param  array|string  $action
146
     * @return array
147
     */
148 36
    protected function getControllerAction($action)
149
    {
150 36
        if (is_string($action) === true) {
151 36
            $action = array('uses' => $action);
152 36
        }
153
154
        // Here we'll get an instance of this controller dispatcher and hand it off to
155
        // the Closure so it will be used to resolve the class instances out of our
156
        // IoC container instance and call the appropriate methods on the class.
157 36
        if (count($this->groupStack) > 0) {
158 4
            $action['uses'] = $this->prependGroupUses($action['uses']);
159 4
        }
160
161
        // Here we'll get an instance of this controller dispatcher and hand it off to
162
        // the Closure so it will be used to resolve the class instances out of our
163
        // IoC container instance and call the appropriate methods on the class.
164 36
        $action['controller'] = $action['uses'];
165
166 36
        $closure = $action['uses'];
167
168 36
        return array_set($action, 'uses', $closure);
169
    }
170
171
    /**
172
     * Replace the string action in the given array with a Closure to call.
173
     *
174
     * @param  array $action
175
     * @return array
176
     */
177 8
    public function makeControllerActionClosure(array $action)
178
    {
179 8
        $closure = $this->getClassClosure($action['uses']);
180
181 8
        return array_set($action, 'uses', $closure);
182
    }
183
184
    /**
185
     * Create a new route instance.
186
     *
187
     * @param  array|string  $methods
188
     * @param  string  $uri
189
     * @param  mixed   $action
190
     * @return \Illuminate\Routing\Route
191
     */
192 44
    protected function createRoute($methods, $uri, $action)
193
    {
194
        // If the route is routing to a controller we will parse the route action into
195
        // an acceptable array format before registering it and creating this route
196
        // instance itself. We need to build the Closure that will call this out.
197 44
        if ($this->routingToController($action) === true) {
198 36
            $action = $this->getControllerAction($action);
199 36
        }
200
201 44
        $route = $this->newRoute(
202 44
            $methods,
203 44
            $uri = $this->prefix($uri),
204
            $action
205 44
        );
206
207
        // If we have groups that need to be merged, we will merge them now after this
208
        // route has already been created and is ready to go. After we're done with
209
        // the merge we will be ready to return the route back out to the caller.
210 44
        if (empty($this->groupStack) === false) {
211 4
            $this->mergeController($route);
212 4
        }
213
214 44
        $this->addWhereClausesToRoute($route);
215
216 44
        return $route;
217
    }
218
219
    /**
220
     * Create a new Route object.
221
     *
222
     * @param  array|string              $methods
223
     * @param  string                    $uri
224
     * @param  mixed                     $action
225
     * @return \Illuminate\Routing\Route
226
     */
227 44
    protected function newRoute($methods, $uri, $action)
228
    {
229 44
        return new Route($methods, $uri, $action);
0 ignored issues
show
Bug introduced by
It seems like $methods defined by parameter $methods on line 227 can also be of type string; however, Illuminate\Routing\Route::__construct() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
230
    }
231
232
    /**
233
     * Add the necessary where clauses to the route based on its initial registration.
234
     *
235
     * @param  \Illuminate\Routing\Route  $route
236
     * @return \Illuminate\Routing\Route
237
     */
238 44
    protected function addWhereClausesToRoute($route)
239
    {
240 44
        $route->where(
241 44
            array_merge($this->patterns, array_get($route->getAction(), 'where', array()))
242 44
        );
243 44
        return $route;
244
    }
245
}
246