Completed
Pull Request — master (#283)
by Phil
02:20
created

Router::buildOptionsRoutes()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.288

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 12
cts 15
cp 0.8
rs 8.8497
c 0
b 0
f 0
cc 6
nc 10
nop 1
crap 6.288
1
<?php
2
3
declare(strict_types=1);
4
5
namespace League\Route;
6
7
use FastRoute\{DataGenerator, RouteCollector, RouteParser};
8
use InvalidArgumentException;
9
use League\Route\Middleware\{MiddlewareAwareInterface, MiddlewareAwareTrait};
10
use League\Route\Strategy\{ApplicationStrategy, OptionsHandlerInterface, StrategyAwareInterface, StrategyAwareTrait};
11
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
12
use Psr\Http\Server\RequestHandlerInterface;
13
use RuntimeException;
14
15
class Router implements
16
    MiddlewareAwareInterface,
17
    RouteCollectionInterface,
18
    StrategyAwareInterface,
19
    RequestHandlerInterface
20
{
21
    use MiddlewareAwareTrait;
22
    use RouteCollectionTrait;
23
    use StrategyAwareTrait;
24
25
    protected const IDENTIFIER_SEPARATOR = "\t";
26
27
    /**
28
     * @var RouteGroup[]
29
     */
30
    protected $groups = [];
31
32
    /**
33
     * @var Route[]
34
     */
35
    protected $namedRoutes = [];
36
37
    /**
38
     * @var array
39
     */
40
    protected $patternMatchers = [
41
        '/{(.+?):number}/'        => '{$1:[0-9]+}',
42
        '/{(.+?):word}/'          => '{$1:[a-zA-Z]+}',
43
        '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}',
44
        '/{(.+?):slug}/'          => '{$1:[a-z0-9-]+}',
45
        '/{(.+?):uuid}/'          => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}'
46
    ];
47
48
    /**
49
     * @var RouteCollector
50
     */
51
    protected $routeCollector;
52
53
    /**
54
     * @var Route[]
55
     */
56
    protected $routes = [];
57
58
    /**
59
     * @var bool
60
     */
61
    protected $routesPrepared = false;
62
63
    /**
64
     * @var array
65
     */
66
    protected $routesData = [];
67
68 66
    public function __construct(?RouteCollector $routeCollector = null)
69
    {
70 66
        $this->routeCollector = $routeCollector ?? new RouteCollector(
71 66
            new RouteParser\Std(),
72 66
            new DataGenerator\GroupCountBased()
73
        );
74 66
    }
75
76 3
    public function addPatternMatcher(string $alias, string $regex): self
77
    {
78 3
        $pattern = '/{(.+?):' . $alias . '}/';
79 3
        $regex = '{$1:' . $regex . '}';
80 3
        $this->patternMatchers[$pattern] = $regex;
81 3
        return $this;
82
    }
83
84 12
    public function group(string $prefix, callable $group): RouteGroup
85
    {
86 12
        $group = new RouteGroup($prefix, $group, $this);
87 12
        $this->groups[] = $group;
88 12
        return $group;
89
    }
90
91 51
    public function dispatch(ServerRequestInterface $request): ResponseInterface
92
    {
93 51
        if (false === $this->routesPrepared) {
94 48
            $this->prepareRoutes($request);
95
        }
96
97
        /** @var Dispatcher $dispatcher */
98 51
        $dispatcher = (new Dispatcher($this->routesData))->setStrategy($this->getStrategy());
99
100 51
        foreach ($this->getMiddlewareStack() as $middleware) {
101 3
            if (is_string($middleware)) {
102 3
                $dispatcher->lazyMiddleware($middleware);
103 3
                continue;
104
            }
105
106 3
            $dispatcher->middleware($middleware);
107
        }
108
109 51
        return $dispatcher->dispatchRequest($request);
110
    }
111
112 6
    public function getNamedRoute(string $name): Route
113
    {
114 6
        $this->buildNameIndex();
115
116 6
        if (isset($this->namedRoutes[$name])) {
117 3
            return $this->namedRoutes[$name];
118
        }
119
120 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
121
    }
122
123
    public function handle(ServerRequestInterface $request): ResponseInterface
124
    {
125
        return $this->dispatch($request);
126
    }
127
128 51
    public function map(string $method, string $path, $handler): Route
129
    {
130 51
        $path  = sprintf('/%s', ltrim($path, '/'));
131 51
        $route = new Route($method, $path, $handler);
132
133 51
        $this->routes[] = $route;
134
135 51
        return $route;
136
    }
137
138 51
    public function prepareRoutes(ServerRequestInterface $request): void
139
    {
140 51
        if ($this->getStrategy() === null) {
141 33
            $this->setStrategy(new ApplicationStrategy());
142
        }
143
144 51
        $this->processGroups($request);
145 51
        $this->buildNameIndex();
146
147 51
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
148 51
        $options = [];
149
150
        /** @var Route $route */
151 51
        foreach ($routes as $key => $route) {
152 45
            if ($route->getStrategy() === null) {
153 36
                $route->setStrategy($this->getStrategy());
154
            }
155
156 45
            $this->routeCollector->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
157
158
            // global strategy must be an OPTIONS handler to automatically generate OPTIONS route
159 45
            if (!($this->getStrategy() instanceof OptionsHandlerInterface)) {
160 36
                continue;
161
            }
162
163
            // need a messy but useful identifier for to determine what methods to respond with on OPTIONS
164 9
            $identifier = $route->getScheme() . static::IDENTIFIER_SEPARATOR . $route->getHost()
165 9
                . static::IDENTIFIER_SEPARATOR . $route->getPort() . static::IDENTIFIER_SEPARATOR . $route->getPath();
166
167
            // if there is a defined OPTIONS route, do not generate one
168 9
            if ('OPTIONS' === $route->getMethod()) {
169
                unset($options[$identifier]);
170
                continue;
171
            }
172
173 9
            if (!isset($options[$identifier])) {
174 9
                $options[$identifier] = [];
175
            }
176
177 9
            $options[$identifier][] = $route->getMethod();
178
        }
179
180 51
        $this->buildOptionsRoutes($options);
181
182 51
        $this->routesPrepared = true;
183 51
        $this->routesData = $this->routeCollector->getData();
184 51
    }
185
186 57
    protected function buildNameIndex(): void
187
    {
188 57
        foreach ($this->routes as $key => $route) {
189 48
            if ($route->getName() !== null) {
190 3
                unset($this->routes[$key]);
191 3
                $this->namedRoutes[$route->getName()] = $route;
192
            }
193
        }
194 57
    }
195
196 51
    protected function buildOptionsRoutes(array $options): void
197
    {
198 51
        if (!($this->getStrategy() instanceof OptionsHandlerInterface)) {
199 39
            return;
200
        }
201
202
        /** @var OptionsHandlerInterface $strategy */
203 12
        $strategy = $this->getStrategy();
204
205 12
        foreach ($options as $identifier => $methods) {
206 9
            [$scheme, $host, $port, $path] = explode(static::IDENTIFIER_SEPARATOR, $identifier);
0 ignored issues
show
Bug introduced by
The variable $scheme does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $host does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $port does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $path does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
207 9
            $route = new Route('OPTIONS', $path, $strategy->getOptionsCallable($methods));
208
209 9
            if (!empty($scheme)) {
210
                $route->setScheme($scheme);
211
            }
212
213 9
            if (!empty($host)) {
214
                $route->setHost($host);
215
            }
216
217 9
            if (!empty($port)) {
218
                $route->setPort($port);
219
            }
220
221 9
            $this->routeCollector->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
222
        }
223 12
    }
224
225 51
    protected function processGroups(ServerRequestInterface $request): void
226
    {
227 51
        $activePath = $request->getUri()->getPath();
228
229 51
        foreach ($this->groups as $key => $group) {
230
            // we want to determine if we are technically in a group even if the
231
            // route is not matched so exceptions are handled correctly
232
            if (
233 9
                $group->getStrategy() !== null
234 9
                && strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0
235
            ) {
236 6
                $this->setStrategy($group->getStrategy());
237
            }
238
239 9
            unset($this->groups[$key]);
240 9
            $group();
241
        }
242 51
    }
243
244 45
    protected function parseRoutePath(string $path): string
245
    {
246 45
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
247
    }
248
}
249