Completed
Push — master ( 216b02...3062b3 )
by Phil
116:53 queued 108:43
created

RouteCollection::parseRoutePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 InvalidArgumentException;
12
use League\Container\Container;
13
use League\Route\Middleware\ExecutionChain;
14
use League\Route\Middleware\StackAwareInterface as MiddlewareAwareInterface;
15
use League\Route\Middleware\StackAwareTrait as MiddlewareAwareTrait;
16
use League\Route\Strategy\ApplicationStrategy;
17
use League\Route\Strategy\StrategyAwareInterface;
18
use League\Route\Strategy\StrategyAwareTrait;
19
use Psr\Container\ContainerInterface;
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 \Psr\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 \Psr\Container\ContainerInterface $container
67
     * @param \FastRoute\RouteParser            $parser
68
     * @param \FastRoute\DataGenerator          $generator
69
     */
70 45
    public function __construct(
71 1
        ContainerInterface $container = null,
72
        RouteParser        $parser    = null,
73
        DataGenerator      $generator = null
74
    ) {
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 6
        }
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 5
        }
149
150 27
        $this->prepRoutes($request);
151
152 27
        return (new Dispatcher($this->getData()))->setStrategy($this->getStrategy());
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
            // check for port condition
181 21
            if (! is_null($route->getPort()) && $route->getPort() !== $request->getUri()->getPort()) {
182
                continue;
183
            }
184
185 21
            $route->setContainer($this->container);
186
187 21
            if (is_null($route->getStrategy())) {
188 21
                $route->setStrategy($this->getStrategy());
189 7
            }
190
191 21
            $this->addRoute(
192 21
                $route->getMethods(),
193 21
                $this->parseRoutePath($route->getPath()),
194 21
                [$route, 'getExecutionChain']
195 7
            );
196 9
        }
197 27
    }
198
199
    /**
200
     * Build an index of named routes.
201
     *
202
     * @return void
203
     */
204 36
    protected function buildNameIndex()
205
    {
206 36
        $this->processGroups();
207
208 36
        foreach ($this->routes as $key => $route) {
209 27
            if (! is_null($route->getName())) {
210 6
                unset($this->routes[$key]);
211 20
                $this->namedRoutes[$route->getName()] = $route;
212 2
            }
213 12
        }
214 36
    }
215
216
    /**
217
     * Process all groups.
218
     *
219
     * @return void
220
     */
221 36
    protected function processGroups()
222
    {
223 36
        foreach ($this->groups as $key => $group) {
224 3
            unset($this->groups[$key]);
225 3
            $group();
226 12
        }
227 36
    }
228
229
    /**
230
     * Get named route.
231
     *
232
     * @param string $name
233
     *
234
     * @return \League\Route\Route
235
     */
236 9
    public function getNamedRoute($name)
237
    {
238 9
        $this->buildNameIndex();
239
240 9
        if (array_key_exists($name, $this->namedRoutes)) {
241 6
            return $this->namedRoutes[$name];
242
        }
243
244 3
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
245
    }
246
247
    /**
248
     * Add a convenient pattern matcher to the internal array for use with all routes.
249
     *
250
     * @param string $alias
251
     * @param string $regex
252
     *
253
     * @return void
254
     */
255 3
    public function addPatternMatcher($alias, $regex)
256
    {
257 3
        $pattern = '/{(.+?):' . $alias . '}/';
258 3
        $regex   = '{$1:' . $regex . '}';
259
260 3
        $this->patternMatchers[$pattern] = $regex;
261 3
    }
262
263
    /**
264
     * Convenience method to convert pre-defined key words in to regex strings.
265
     *
266
     * @param string $path
267
     *
268
     * @return string
269
     */
270 21
    protected function parseRoutePath($path)
271
    {
272 21
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
273
    }
274
}
275