Completed
Pull Request — master (#115)
by Phil
03:12
created

RouteCollection::addPatternMatcher()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
crap 1
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\MiddlewareAwareInterface;
14
use League\Route\Middleware\MiddlewareAwareTrait;
15
use League\Route\Strategy\RequestResponseStrategy;
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 33
    public function __construct(
68
        ContainerInterface $container = null,
69
        RouteParser        $parser    = null,
70 3
        DataGenerator      $generator = null
71
    ) {
72 33
        $this->container = ($container instanceof ContainerInterface) ? $container : new Container;
73
74
        // build parent route collector
75 33
        $parser    = ($parser instanceof RouteParser) ? $parser : new StdRouteParser;
76 33
        $generator = ($generator instanceof DataGenerator) ? $generator : new GroupCountBasedDataGenerator;
77 33
        parent::__construct($parser, $generator);
78 33
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 18
    public function map($method, $path, $handler)
84
    {
85 18
        $path             = sprintf('/%s', ltrim($path, '/'));
86 18
        $route            = (new Route)->setMethods((array) $method)->setPath($path)->setCallable($handler);
87 18
        $this->routes[]   = $route;
88
89 18
        $route->setMiddlewareRunner($this->getMiddlewareRunner());
90
91 18
        return $route;
92
    }
93
94
    /**
95
     * Add a group of routes to the collection.
96
     *
97
     * @param string   $prefix
98
     * @param callable $group
99
     *
100
     * @return \League\Route\RouteGroup
101
     */
102 3
    public function group($prefix, callable $group)
103
    {
104 3
        $group            = new RouteGroup($prefix, $group, $this);
105 3
        $this->groups[]   = $group;
106
107 3
        $group->setMiddlewareRunner($this->getMiddlewareRunner());
108
109 3
        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 15
    public function dispatch(ServerRequestInterface $request, ResponseInterface $response)
121
    {
122 15
        $dispatcher = $this->getDispatcher($request);
123
124 15
        return $dispatcher->handle($request, $response);
125
    }
126
127
    /**
128
     * Return a fully configured dispatcher.
129
     *
130
     * @param \Psr\Http\Message\ServerRequestInterface $request
131
     *
132
     * @return \League\Route\Dispatcher
133
     */
134 18
    public function getDispatcher(ServerRequestInterface $request)
135
    {
136 18
        if (is_null($this->getStrategy())) {
137 12
            $this->setStrategy(new RequestResponseStrategy);
138 12
        }
139
140 18
        $this->prepRoutes($request);
141
142 18
        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...
143
    }
144
145
    /**
146
     * Prepare all routes, build name index and filter out none matching
147
     * routes before being passed off to the parser.
148
     *
149
     * @param \Psr\Http\Message\ServerRequestInterface $request
150
     *
151
     * @return void
152
     */
153 18
    protected function prepRoutes(ServerRequestInterface $request)
154
    {
155 18
        $this->buildNameIndex();
156
157 18
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
158
159 18
        foreach ($routes as $key => $route) {
160
            // check for scheme condition
161 12
            if (! is_null($route->getScheme()) && $route->getScheme() !== $request->getUri()->getScheme()) {
162 3
                continue;
163
            }
164
165
            // check for domain condition
166 12
            if (! is_null($route->getHost()) && $route->getHost() !== $request->getUri()->getHost()) {
167 3
                continue;
168
            }
169
170 12
            $route->setContainer($this->container);
171
172 12
            if (is_null($route->getStrategy())) {
173 12
                $route->setStrategy($this->getStrategy());
174 12
            }
175
176 12
            $this->addRoute(
177 12
                $route->getMethods(),
178 12
                $this->parseRoutePath($route->getPath()),
179 12
                [$route, 'dispatch']
180 12
            );
181 18
        }
182 18
    }
183
184
    /**
185
     * Build an index of named routes.
186
     *
187
     * @return void
188
     */
189 27
    protected function buildNameIndex()
190
    {
191 27
        $this->processGroups();
192
193 27
        foreach ($this->routes as $key => $route) {
194 18
            if (! is_null($route->getName())) {
195 9
                unset($this->routes[$key]);
196 9
                $this->namedRoutes[$route->getName()] = $route;
197 9
            }
198 27
        }
199 27
    }
200
201
    /**
202
     * Process all groups.
203
     *
204
     * @return void
205
     */
206 27
    protected function processGroups()
207
    {
208 27
        foreach ($this->groups as $key => $group) {
209 3
            unset($this->groups[$key]);
210 3
            $group();
211 27
        }
212 27
    }
213
214
    /**
215
     * Get named route.
216
     *
217
     * @param string $name
218
     *
219
     * @return \League\Route\Route
220
     */
221 9
    public function getNamedRoute($name)
222
    {
223 9
        $this->buildNameIndex();
224
225 9
        if (array_key_exists($name, $this->namedRoutes)) {
226 6
            return $this->namedRoutes[$name];
227
        }
228
229 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
230
    }
231
232
    /**
233
     * Add a convenient pattern matcher to the internal array for use with all routes.
234
     *
235
     * @param string $alias
236
     * @param string $regex
237
     *
238
     * @return void
239
     */
240 3
    public function addPatternMatcher($alias, $regex)
241
    {
242 3
        $pattern = '/{(.+?):' . $alias . '}/';
243 3
        $regex   = '{$1:' . $regex . '}';
244
245 3
        $this->patternMatchers[$pattern] = $regex;
246 3
    }
247
248
    /**
249
     * Convenience method to convert pre-defined key words in to regex strings.
250
     *
251
     * @param string $path
252
     *
253
     * @return string
254
     */
255 12
    protected function parseRoutePath($path)
256
    {
257 12
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 3
    public function before(callable $middleware)
264
    {
265 3
        $this->getMiddlewareRunner()->before($middleware);
266
267 3
        return $this;
268
    }
269
270
    /**
271
     * {@inheritdoc}
272
     */
273 3
    public function after(callable $middleware)
274
    {
275 3
        $this->getMiddlewareRunner()->after($middleware);
276
277 3
        return $this;
278
    }
279
}
280