Completed
Push — master ( b3457c...c694d9 )
by Phil
02:30
created

src/Router.php (2 issues)

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 declare(strict_types=1);
2
3
namespace League\Route;
4
5
use InvalidArgumentException;
6
use FastRoute\{DataGenerator, RouteCollector, RouteParser};
7
use League\Route\Strategy\{ApplicationStrategy, StrategyAwareInterface, StrategyAwareTrait};
8
use League\Route\Middleware\{MiddlewareAwareInterface, MiddlewareAwareTrait};
9
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
10
11
class Router extends RouteCollector implements
12
    MiddlewareAwareInterface,
13
    RouteCollectionInterface,
14
    StrategyAwareInterface
15
{
16
    use MiddlewareAwareTrait;
17
    use RouteCollectionTrait;
18
    use StrategyAwareTrait;
19
20
    /**
21
     * @var \League\Route\Route[]
22
     */
23
    protected $routes = [];
24
25
    /**
26
     * @var \League\Route\Route[]
27
     */
28
    protected $namedRoutes = [];
29
30
    /**
31
     * @var \League\Route\RouteGroup[]
32
     */
33
    protected $groups = [];
34
35
    /**
36
     * @var array
37
     */
38
    protected $patternMatchers = [
39
        '/{(.+?):number}/'        => '{$1:[0-9]+}',
40
        '/{(.+?):word}/'          => '{$1:[a-zA-Z]+}',
41
        '/{(.+?):alphanum_dash}/' => '{$1:[a-zA-Z0-9-_]+}',
42
        '/{(.+?):slug}/'          => '{$1:[a-z0-9-]+}',
43
        '/{(.+?):uuid}/'          => '{$1:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}+}'
44
    ];
45
46
    /**
47
     * Constructor
48
     *
49
     * @param \FastRoute\RouteParser   $parser
50
     * @param \FastRoute\DataGenerator $generator
51
     */
52 40
    public function __construct(?RouteParser $parser = null, ?DataGenerator $generator = null)
53
    {
54
        // build parent route collector
55 40
        $parser    = ($parser) ?? new RouteParser\Std;
56 40
        $generator = ($generator) ?? new DataGenerator\GroupCountBased;
57 40
        parent::__construct($parser, $generator);
58 40
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 30
    public function map(string $method, string $path, $handler) : Route
64
    {
65 30
        $path  = sprintf('/%s', ltrim($path, '/'));
66 30
        $route = new Route($method, $path, $handler);
67
68 30
        $this->routes[] = $route;
69
70 30
        return $route;
71
    }
72
73
    /**
74
     * Add a group of routes to the collection
75
     *
76
     * @param string   $prefix
77
     * @param callable $group
78
     *
79
     * @return \League\Route\RouteGroup
80
     */
81 8
    public function group(string $prefix, callable $group) : RouteGroup
82
    {
83 8
        $group          = new RouteGroup($prefix, $group, $this);
84 8
        $this->groups[] = $group;
85
86 8
        return $group;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 30
    public function dispatch(ServerRequestInterface $request) : ResponseInterface
93
    {
94 30
        if (is_null($this->getStrategy())) {
95 18
            $this->setStrategy(new ApplicationStrategy);
96
        }
97
98 30
        $this->prepRoutes($request);
99
100 30
        return (new Dispatcher($this->getData()))
101 30
            ->middlewares($this->getMiddlewareStack())
102 30
            ->setStrategy($this->getStrategy())
103 30
            ->dispatchRequest($request)
104
        ;
105
    }
106
107
    /**
108
     * Prepare all routes, build name index and filter out none matching
109
     * routes before being passed off to the parser.
110
     *
111
     * @param \Psr\Http\Message\ServerRequestInterface $request
112
     *
113
     * @return void
114
     */
115 30
    protected function prepRoutes(ServerRequestInterface $request) : void
116
    {
117 30
        $this->processGroups($request);
118 30
        $this->buildNameIndex();
119
120 30
        $routes = array_merge(array_values($this->routes), array_values($this->namedRoutes));
121
122 30
        foreach ($routes as $key => $route) {
123
            // check for scheme condition
124 26
            if (! is_null($route->getScheme()) && $route->getScheme() !== $request->getUri()->getScheme()) {
125 2
                continue;
126
            }
127
128
            // check for domain condition
129 24
            if (! is_null($route->getHost()) && $route->getHost() !== $request->getUri()->getHost()) {
130 2
                continue;
131
            }
132
133
            // check for port condition
134 22
            if (! is_null($route->getPort()) && $route->getPort() !== $request->getUri()->getPort()) {
135 2
                continue;
136
            }
137
138 20
            if (is_null($route->getStrategy())) {
139 14
                $route->setStrategy($this->getStrategy());
140
            }
141
142 20
            $this->addRoute($route->getMethod(), $this->parseRoutePath($route->getPath()), $route);
143
        }
144 30
    }
145
146
    /**
147
     * Build an index of named routes.
148
     *
149
     * @return void
150
     */
151 34
    protected function buildNameIndex() : void
152
    {
153 34
        foreach ($this->routes as $key => $route) {
154 28
            if (! is_null($route->getName())) {
1 ignored issue
show
Consider using $route->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
155 2
                unset($this->routes[$key]);
156 28
                $this->namedRoutes[$route->getName()] = $route;
1 ignored issue
show
Consider using $route->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
157
            }
158
        }
159 34
    }
160
161
    /**
162
     * Process all groups
163
     *
164
     * Adds all of the group routes to the collection and determines if the group
165
     * strategy should be be used.
166
     *
167
     * @param \Psr\Http\Message\ServerRequestInterface $request
168
     *
169
     * @return void
170
     */
171 30
    protected function processGroups(ServerRequestInterface $request) : void
172
    {
173 30
        $activePath = $request->getUri()->getPath();
174
175 30
        foreach ($this->groups as $key => $group) {
176
            // we want to determine if we are technically in a group even if the
177
            // route is not matched so exceptions are handled correctly
178 6
            if (strncmp($activePath, $group->getPrefix(), strlen($group->getPrefix())) === 0
179 6
                && ! is_null($group->getStrategy())
180
            ) {
181 4
                $this->setStrategy($group->getStrategy());
182
            }
183
184 6
            unset($this->groups[$key]);
185 6
            $group();
186
        }
187 30
    }
188
189
    /**
190
     * Get a named route
191
     *
192
     * @param string $name
193
     *
194
     * @throws \InvalidArgumentException when no route of the provided name exists.
195
     *
196
     * @return \League\Route\Route
197
     */
198 4
    public function getNamedRoute(string $name) : Route
199
    {
200 4
        $this->buildNameIndex();
201
202 4
        if (isset($this->namedRoutes[$name])) {
203 2
            return $this->namedRoutes[$name];
204
        }
205
206 2
        throw new InvalidArgumentException(sprintf('No route of the name (%s) exists', $name));
207
    }
208
209
    /**
210
     * Add a convenient pattern matcher to the internal array for use with all routes
211
     *
212
     * @param string $alias
213
     * @param string $regex
214
     *
215
     * @return self
216
     */
217 2
    public function addPatternMatcher(string $alias, string $regex) : self
218
    {
219 2
        $pattern = '/{(.+?):' . $alias . '}/';
220 2
        $regex   = '{$1:' . $regex . '}';
221
222 2
        $this->patternMatchers[$pattern] = $regex;
223
224 2
        return $this;
225
    }
226
227
    /**
228
     * Replace word patterns with regex in route path
229
     *
230
     * @param string $path
231
     *
232
     * @return string
233
     */
234 20
    protected function parseRoutePath(string $path) : string
235
    {
236 20
        return preg_replace(array_keys($this->patternMatchers), array_values($this->patternMatchers), $path);
237
    }
238
}
239