Completed
Pull Request — master (#115)
by Phil
02:51
created

RouteCollection::getDispatcher()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 5
Bugs 2 Features 1
Metric Value
c 5
b 2
f 1
dl 0
loc 10
ccs 0
cts 8
cp 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 6
1
<?php
2
3
namespace League\Route;
4
5
use FastRoute\DataGenerator;
6
use FastRoute\DataGenerator\GroupCountBased as GroupCountBasedDataGenerator;
7
use FastRoute\RouteCollector;
8
use FastRoute\RouteParser;
9
use FastRoute\RouteParser\Std as StdRouteParser;
10
use Interop\Container\ContainerInterface;
11
use InvalidArgumentException;
12
use League\Container\Container;
13
use League\Route\Middleware\StackAwareInterface as MiddlewareAwareInterface;
14
use League\Route\Middleware\StackAwareTrait as MiddlewareAwareTrait;
15
use League\Route\Strategy\ApplicationStrategy;
16
use League\Route\Strategy\StrategyAwareInterface;
17
use League\Route\Strategy\StrategyAwareTrait;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
21
class RouteCollection
22
    extends RouteCollector
23
    implements MiddlewareAwareInterface, RouteCollectionInterface, StrategyAwareInterface
24
{
25
    use MiddlewareAwareTrait;
26
    use RouteCollectionMapTrait;
27
    use StrategyAwareTrait;
28
29
    /**
30
     * @var \Interop\Container\ContainerInterface
31
     */
32
    protected $container;
33
34
    /**
35
     * @var \League\Route\Route[]
36
     */
37
    protected $routes = [];
38
39
    /**
40
     * @var \League\Route\Route[]
41
     */
42
    protected $namedRoutes = [];
43
44
    /**
45
     * @var \League\Route\RouteGroup[]
46
     */
47
    protected $groups = [];
48
49
    /**
50
     * @var array
51
     */
52
    protected $patternMatchers = [
53
        '/{(.+?):number}/'        => '{$1:[0-9]+}',
54
        '/{(.+?):word}/'          => '{$1:[a-zA-Z]+}',
55
        '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}',
56
        '/{(.+?):slug}/'          => '{$1:[a-z0-9-]+}',
57
        '/{(.+?):uuid}/'          => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}'
58
    ];
59
60
    /**
61
     * Constructor.
62
     *
63
     * @param \Interop\Container\ContainerInterface $container
64
     * @param \FastRoute\RouteParser                $parser
65
     * @param \FastRoute\DataGenerator              $generator
66
     */
67
    public function __construct(
68
        ContainerInterface $container = null,
69
        RouteParser        $parser    = null,
70
        DataGenerator      $generator = null
71
    ) {
72
        $this->container = ($container instanceof ContainerInterface) ? $container : new Container;
73
74
        // build parent route collector
75
        $parser    = ($parser instanceof RouteParser) ? $parser : new StdRouteParser;
76
        $generator = ($generator instanceof DataGenerator) ? $generator : new GroupCountBasedDataGenerator;
77
        parent::__construct($parser, $generator);
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function map($method, $path, $handler)
84
    {
85
        $path  = sprintf('/%s', ltrim($path, '/'));
86
        $route = (new Route)->setMethods((array) $method)->setPath($path)->setCallable($handler);
87
88
        $this->routes[] = $route;
89
90
        return $route;
91
    }
92
93
    /**
94
     * Add a group of routes to the collection.
95
     *
96
     * @param string   $prefix
97
     * @param callable $group
98
     *
99
     * @return \League\Route\RouteGroup
100
     */
101
    public function group($prefix, callable $group)
102
    {
103
        $group          = new RouteGroup($prefix, $group, $this);
104
        $this->groups[] = $group;
105
106
        return $group;
107
    }
108
109
    /**
110
     * Dispatch the route based on the request.
111
     *
112
     * @param \Psr\Http\Message\ServerRequestInterface $request
113
     * @param \Psr\Http\Message\ResponseInterface      $response
114
     *
115
     * @return \Psr\Http\Message\ResponseInterface
116
     */
117
    public function dispatch(ServerRequestInterface $request, ResponseInterface $response)
118
    {
119
        $dispatcher = $this->getDispatcher($request);
120
        $execChain  = $dispatcher->handle($request);
121
122
        foreach ($this->getMiddlewareStack() as $middleware) {
123
            $execChain->middleware($middleware);
124
        }
125
126
        return $execChain->execute($request, $response);
127
    }
128
129
    /**
130
     * Return a fully configured dispatcher.
131
     *
132
     * @param \Psr\Http\Message\ServerRequestInterface $request
133
     *
134
     * @return \League\Route\Dispatcher
135
     */
136
    public function getDispatcher(ServerRequestInterface $request)
137
    {
138
        if (is_null($this->getStrategy())) {
139
            $this->setStrategy(new ApplicationStrategy);
140
        }
141
142
        $this->prepRoutes($request);
143
144
        return (new Dispatcher($this->getData()))->setStrategy($this->getStrategy());
0 ignored issues
show
Bug introduced by
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...
145
    }
146
147
    /**
148
     * Prepare all routes, build name index and filter out none matching
149
     * routes before being passed off to the parser.
150
     *
151
     * @param \Psr\Http\Message\ServerRequestInterface $request
152
     *
153
     * @return void
154
     */
155
    protected function prepRoutes(ServerRequestInterface $request)
156
    {
157
        $this->buildNameIndex();
158
159
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
160
161
        foreach ($routes as $key => $route) {
162
            // check for scheme condition
163
            if (! is_null($route->getScheme()) && $route->getScheme() !== $request->getUri()->getScheme()) {
164
                continue;
165
            }
166
167
            // check for domain condition
168
            if (! is_null($route->getHost()) && $route->getHost() !== $request->getUri()->getHost()) {
169
                continue;
170
            }
171
172
            $route->setContainer($this->container);
173
174
            if (is_null($route->getStrategy())) {
175
                $route->setStrategy($this->getStrategy());
176
            }
177
178
            $this->addRoute(
179
                $route->getMethods(),
180
                $this->parseRoutePath($route->getPath()),
181
                [$route, 'getExecutionChain']
182
            );
183
        }
184
    }
185
186
    /**
187
     * Build an index of named routes.
188
     *
189
     * @return void
190
     */
191
    protected function buildNameIndex()
192
    {
193
        $this->processGroups();
194
195
        foreach ($this->routes as $key => $route) {
196
            if (! is_null($route->getName())) {
197
                unset($this->routes[$key]);
198
                $this->namedRoutes[$route->getName()] = $route;
199
            }
200
        }
201
    }
202
203
    /**
204
     * Process all groups.
205
     *
206
     * @return void
207
     */
208
    protected function processGroups()
209
    {
210
        foreach ($this->groups as $key => $group) {
211
            unset($this->groups[$key]);
212
            $group();
213
        }
214
    }
215
216
    /**
217
     * Get named route.
218
     *
219
     * @param string $name
220
     *
221
     * @return \League\Route\Route
222
     */
223
    public function getNamedRoute($name)
224
    {
225
        $this->buildNameIndex();
226
227
        if (array_key_exists($name, $this->namedRoutes)) {
228
            return $this->namedRoutes[$name];
229
        }
230
231
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
232
    }
233
234
    /**
235
     * Add a convenient pattern matcher to the internal array for use with all routes.
236
     *
237
     * @param string $alias
238
     * @param string $regex
239
     *
240
     * @return void
241
     */
242
    public function addPatternMatcher($alias, $regex)
243
    {
244
        $pattern = '/{(.+?):' . $alias . '}/';
245
        $regex   = '{$1:' . $regex . '}';
246
247
        $this->patternMatchers[$pattern] = $regex;
248
    }
249
250
    /**
251
     * Convenience method to convert pre-defined key words in to regex strings.
252
     *
253
     * @param string $path
254
     *
255
     * @return string
256
     */
257
    protected function parseRoutePath($path)
258
    {
259
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
260
    }
261
}
262