Completed
Push — master ( f5bdea...18af5f )
by Phil
14s
created

src/RouteCollection.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace League\Route;
4
5
use Exception;
6
use FastRoute\DataGenerator;
7
use FastRoute\DataGenerator\GroupCountBased as GroupCountBasedDataGenerator;
8
use FastRoute\RouteCollector;
9
use FastRoute\RouteParser;
10
use FastRoute\RouteParser\Std as StdRouteParser;
11
use Interop\Container\ContainerInterface;
12
use InvalidArgumentException;
13
use League\Container\Container;
14
use League\Route\Middleware\ExecutionChain;
15
use League\Route\Middleware\StackAwareInterface as MiddlewareAwareInterface;
16
use League\Route\Middleware\StackAwareTrait as MiddlewareAwareTrait;
17
use League\Route\Strategy\ApplicationStrategy;
18
use League\Route\Strategy\StrategyAwareInterface;
19
use League\Route\Strategy\StrategyAwareTrait;
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
23
class RouteCollection extends RouteCollector implements
24
    MiddlewareAwareInterface,
25
    RouteCollectionInterface,
26
    StrategyAwareInterface
27
{
28
    use MiddlewareAwareTrait;
29
    use RouteCollectionMapTrait;
30
    use StrategyAwareTrait;
31
32
    /**
33
     * @var \Interop\Container\ContainerInterface
34
     */
35
    protected $container;
36
37
    /**
38
     * @var \League\Route\Route[]
39
     */
40
    protected $routes = [];
41
42
    /**
43
     * @var \League\Route\Route[]
44
     */
45
    protected $namedRoutes = [];
46
47
    /**
48
     * @var \League\Route\RouteGroup[]
49
     */
50
    protected $groups = [];
51
52
    /**
53
     * @var array
54
     */
55
    protected $patternMatchers = [
56
        '/{(.+?):number}/'        => '{$1:[0-9]+}',
57
        '/{(.+?):word}/'          => '{$1:[a-zA-Z]+}',
58
        '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}',
59
        '/{(.+?):slug}/'          => '{$1:[a-z0-9-]+}',
60
        '/{(.+?):uuid}/'          => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}'
61
    ];
62
63
    /**
64
     * Constructor.
65
     *
66
     * @param \Interop\Container\ContainerInterface $container
67
     * @param \FastRoute\RouteParser                $parser
68
     * @param \FastRoute\DataGenerator              $generator
69
     */
70 45
    public function __construct(
71
        ContainerInterface $container = null,
72
        RouteParser        $parser    = null,
73
        DataGenerator      $generator = null
74 3
    ) {
75 45
        $this->container = ($container instanceof ContainerInterface) ? $container : new Container;
76
77
        // build parent route collector
78 45
        $parser    = ($parser instanceof RouteParser) ? $parser : new StdRouteParser;
79 45
        $generator = ($generator instanceof DataGenerator) ? $generator : new GroupCountBasedDataGenerator;
80 45
        parent::__construct($parser, $generator);
81 45
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 30
    public function map($method, $path, $handler)
87
    {
88 30
        $path  = sprintf('/%s', ltrim($path, '/'));
89 30
        $route = (new Route)->setMethods((array) $method)->setPath($path)->setCallable($handler);
90
91 30
        $this->routes[] = $route;
92
93 30
        return $route;
94
    }
95
96
    /**
97
     * Add a group of routes to the collection.
98
     *
99
     * @param string   $prefix
100
     * @param callable $group
101
     *
102
     * @return \League\Route\RouteGroup
103
     */
104 6
    public function group($prefix, callable $group)
105
    {
106 6
        $group          = new RouteGroup($prefix, $group, $this);
107 6
        $this->groups[] = $group;
108
109 6
        return $group;
110
    }
111
112
    /**
113
     * Dispatch the route based on the request.
114
     *
115
     * @param \Psr\Http\Message\ServerRequestInterface $request
116
     * @param \Psr\Http\Message\ResponseInterface      $response
117
     *
118
     * @return \Psr\Http\Message\ResponseInterface
119
     */
120 24
    public function dispatch(ServerRequestInterface $request, ResponseInterface $response)
121
    {
122 24
        $dispatcher = $this->getDispatcher($request);
123 24
        $execChain  = $dispatcher->handle($request);
124
125 18
        foreach ($this->getMiddlewareStack() as $middleware) {
126
            $execChain->middleware($middleware);
127 18
        }
128
129
        try {
130 18
            return $execChain->execute($request, $response);
131 9
        } catch (Exception $exception) {
132 9
            $middleware = $this->getStrategy()->getExceptionDecorator($exception);
133 6
            return (new ExecutionChain)->middleware($middleware)->execute($request, $response);
134
        }
135
    }
136
137
    /**
138
     * Return a fully configured dispatcher.
139
     *
140
     * @param \Psr\Http\Message\ServerRequestInterface $request
141
     *
142
     * @return \League\Route\Dispatcher
143
     */
144 27
    public function getDispatcher(ServerRequestInterface $request)
145
    {
146 27
        if (is_null($this->getStrategy())) {
147 15
            $this->setStrategy(new ApplicationStrategy);
148 15
        }
149
150 27
        $this->prepRoutes($request);
151
152 27
        return (new Dispatcher($this->getData()))->setStrategy($this->getStrategy());
0 ignored issues
show
It seems like $this->getStrategy() can be null; however, setStrategy() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
153
    }
154
155
    /**
156
     * Prepare all routes, build name index and filter out none matching
157
     * routes before being passed off to the parser.
158
     *
159
     * @param \Psr\Http\Message\ServerRequestInterface $request
160
     *
161
     * @return void
162
     */
163 27
    protected function prepRoutes(ServerRequestInterface $request)
164
    {
165 27
        $this->buildNameIndex();
166
167 27
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
168
169 27
        foreach ($routes as $key => $route) {
170
            // check for scheme condition
171 21
            if (! is_null($route->getScheme()) && $route->getScheme() !== $request->getUri()->getScheme()) {
172 3
                continue;
173
            }
174
175
            // check for domain condition
176 21
            if (! is_null($route->getHost()) && $route->getHost() !== $request->getUri()->getHost()) {
177 3
                continue;
178
            }
179
180 21
            $route->setContainer($this->container);
181
182 21
            if (is_null($route->getStrategy())) {
183 21
                $route->setStrategy($this->getStrategy());
184 21
            }
185
186 21
            $this->addRoute(
187 21
                $route->getMethods(),
188 21
                $this->parseRoutePath($route->getPath()),
189 21
                [$route, 'getExecutionChain']
190 21
            );
191 27
        }
192 27
    }
193
194
    /**
195
     * Build an index of named routes.
196
     *
197
     * @return void
198
     */
199 36
    protected function buildNameIndex()
200
    {
201 36
        $this->processGroups();
202
203 36
        foreach ($this->routes as $key => $route) {
204 27
            if (! is_null($route->getName())) {
205 6
                unset($this->routes[$key]);
206 6
                $this->namedRoutes[$route->getName()] = $route;
207 6
            }
208 36
        }
209 36
    }
210
211
    /**
212
     * Process all groups.
213
     *
214
     * @return void
215
     */
216 36
    protected function processGroups()
217
    {
218 36
        foreach ($this->groups as $key => $group) {
219 3
            unset($this->groups[$key]);
220 3
            $group();
221 36
        }
222 36
    }
223
224
    /**
225
     * Get named route.
226
     *
227
     * @param string $name
228
     *
229
     * @return \League\Route\Route
230
     */
231 9
    public function getNamedRoute($name)
232
    {
233 9
        $this->buildNameIndex();
234
235 9
        if (array_key_exists($name, $this->namedRoutes)) {
236 6
            return $this->namedRoutes[$name];
237
        }
238
239 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
240
    }
241
242
    /**
243
     * Add a convenient pattern matcher to the internal array for use with all routes.
244
     *
245
     * @param string $alias
246
     * @param string $regex
247
     *
248
     * @return void
249
     */
250 3
    public function addPatternMatcher($alias, $regex)
251
    {
252 3
        $pattern = '/{(.+?):' . $alias . '}/';
253 3
        $regex   = '{$1:' . $regex . '}';
254
255 3
        $this->patternMatchers[$pattern] = $regex;
256 3
    }
257
258
    /**
259
     * Convenience method to convert pre-defined key words in to regex strings.
260
     *
261
     * @param string $path
262
     *
263
     * @return string
264
     */
265 21
    protected function parseRoutePath($path)
266
    {
267 21
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
268
    }
269
}
270