Completed
Pull Request — master (#283)
by Phil
22:36
created

Router::buildOptionsRoutes()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8497
c 0
b 0
f 0
ccs 10
cts 10
cp 1
cc 6
nc 10
nop 1
crap 6
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 63
     * @var Route[]
55
     */
56
    protected $routes = [];
57 63
58 63
    /**
59 63
     * @var bool
60 63
     */
61
    protected $routesPrepared = false;
62
63
    /**
64
     * @var array
65 45
     */
66
    protected $routesData = [];
67 45
68 45
    public function __construct(?RouteCollector $routeCollector = null)
69
    {
70 45
        $this->routeCollector = $routeCollector ?? new RouteCollector(
71
            new RouteParser\Std(),
72 45
            new DataGenerator\GroupCountBased()
73
        );
74
    }
75
76
    public function addPatternMatcher(string $alias, string $regex): self
77
    {
78
        $pattern = '/{(.+?):' . $alias . '}/';
79
        $regex = '{$1:' . $regex . '}';
80
        $this->patternMatchers[$pattern] = $regex;
81
        return $this;
82
    }
83 12
84
    public function group(string $prefix, callable $group): RouteGroup
85 12
    {
86 12
        $group = new RouteGroup($prefix, $group, $this);
87
        $this->groups[] = $group;
88 12
        return $group;
89
    }
90
91
    public function dispatch(ServerRequestInterface $request): ResponseInterface
92
    {
93
        if (false === $this->routesPrepared) {
94 48
            $this->prepareRoutes($request);
95
        }
96 48
97 30
        /** @var Dispatcher $dispatcher */
98
        $dispatcher = (new Dispatcher($this->routesData))->setStrategy($this->getStrategy());
99
100 48
        foreach ($this->getMiddlewareStack() as $middleware) {
101
            if (is_string($middleware)) {
102
                $dispatcher->lazyMiddleware($middleware);
103 48
                continue;
104
            }
105 48
106 3
            $dispatcher->middleware($middleware);
107 3
        }
108 3
109
        return $dispatcher->dispatchRequest($request);
110
    }
111 3
112
    public function getNamedRoute(string $name): Route
113
    {
114 48
        $this->buildNameIndex();
115
116
        if (isset($this->namedRoutes[$name])) {
117
            return $this->namedRoutes[$name];
118
        }
119
120
        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
    public function map(string $method, string $path, $handler): Route
129
    {
130
        $path  = sprintf('/%s', ltrim($path, '/'));
131
        $route = new Route($method, $path, $handler);
132
133
        $this->routes[] = $route;
134 48
135
        return $route;
136 48
    }
137 48
138
    public function prepareRoutes(ServerRequestInterface $request): void
139 48
    {
140
        if ($this->getStrategy() === null) {
141
            $this->setStrategy(new ApplicationStrategy());
142 48
        }
143
144 39
        $this->processGroups($request);
145 39
        $this->buildNameIndex();
146 3
147
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
148
        $options = [];
149
150 36
        /** @var Route $route */
151 36
        foreach ($routes as $key => $route) {
152 3
            if ($route->getStrategy() === null) {
153
                $route->setStrategy($this->getStrategy());
154
            }
155
156 33
            $this->routeCollector->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
157 33
158 3
            // global strategy must be an OPTIONS handler to automatically generate OPTIONS route
159
            if (!($this->getStrategy() instanceof OptionsHandlerInterface)) {
160
                continue;
161 30
            }
162 21
163
            // need a messy but useful identifier for to determine what methods to respond with on OPTIONS
164
            $identifier = $route->getScheme() . static::IDENTIFIER_SEPARATOR . $route->getHost()
165 30
                . static::IDENTIFIER_SEPARATOR . $route->getPort() . static::IDENTIFIER_SEPARATOR . $route->getPath();
166
167 48
            // if there is a defined OPTIONS route, do not generate one
168
            if ('OPTIONS' === $route->getMethod()) {
169
                unset($options[$identifier]);
170
                continue;
171
            }
172
173
            if (!isset($options[$identifier])) {
174 54
                $options[$identifier] = [];
175
            }
176 54
177 42
            $options[$identifier][] = $route->getMethod();
178 3
        }
179 29
180
        $this->buildOptionsRoutes($options);
181
182 54
        $this->routesPrepared = true;
183
        $this->routesData = $this->routeCollector->getData();
184
    }
185
186
    protected function buildNameIndex(): void
187
    {
188
        foreach ($this->routes as $key => $route) {
189
            if ($route->getName() !== null) {
190
                unset($this->routes[$key]);
191
                $this->namedRoutes[$route->getName()] = $route;
192
            }
193
        }
194 48
    }
195
196 48
    protected function buildOptionsRoutes(array $options): void
197
    {
198 48
        if (!($this->getStrategy() instanceof OptionsHandlerInterface)) {
199
            return;
200
        }
201 9
202 9
        /** @var OptionsHandlerInterface $strategy */
203
        $strategy = $this->getStrategy();
204 6
205
        foreach ($options as $identifier => $methods) {
206
            [$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 9
209
            if (!empty($scheme)) {
210 48
                $route->setScheme($scheme);
211
            }
212
213
            if (!empty($host)) {
214
                $route->setHost($host);
215
            }
216
217
            if (!empty($port)) {
218
                $route->setPort($port);
219
            }
220
221 6
            $this->routeCollector->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
222
        }
223 6
    }
224
225 6
    protected function processGroups(ServerRequestInterface $request): void
226 3
    {
227
        $activePath = $request->getUri()->getPath();
228
229 3
        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
                $group->getStrategy() !== null
234
                && strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0
235
            ) {
236
                $this->setStrategy($group->getStrategy());
237
            }
238
239
            unset($this->groups[$key]);
240 3
            $group();
241
        }
242 3
    }
243 3
244
    protected function parseRoutePath(string $path): string
245 3
    {
246
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
247 3
    }
248
}
249