Completed
Pull Request — master (#115)
by Phil
04:13
created

RouteCollection::addPatternMatcher()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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