Matcher::match()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * slince routing library
4
 * @author Tao <[email protected]>
5
 */
6
namespace Slince\Routing;
7
8
use Psr\Http\Message\ServerRequestInterface;
9
use Slince\Routing\Exception\RouteNotFoundException;
10
use Slince\Routing\Exception\MethodNotAllowedException;
11
12
class Matcher
13
{
14
    /**
15
     * Routes collection
16
     * @var RouteCollection
17
     */
18
    protected $routes;
19
20
    public function __construct(RouteCollection $routes)
21
    {
22
        $this->routes = $routes;
23
    }
24
25
    /**
26
     * Find the route that match the given path from the routes
27
     * @param string $path
28
     * @return Route
29
     */
30
    public function match($path)
31
    {
32
        $path = '/' . ltrim($path, '/');
33
        return $this->doMatch($path);
34
    }
35
36
    /**
37
     * Find the route that match given request
38
     * @param ServerRequestInterface $request
39
     * @return Route
40
     */
41
    public function matchRequest(ServerRequestInterface $request)
42
    {
43
        return $this->doMatch($request);
44
    }
45
46
    /**
47
     * Do match
48
     * @param string|ServerRequestInterface $pathOrRequest
49
     * @return Route
50
     */
51
    protected function doMatch($pathOrRequest)
52
    {
53
        $route = $pathOrRequest instanceof ServerRequestInterface
54
            ? $this->findRouteFromRequest($pathOrRequest)
55
            : $this->findRoute($pathOrRequest);
56
        $computedParameters = $this->computeRouteParameters($route);
57
        $route->setComputedParameters($computedParameters);
58
        return $route;
59
    }
60
61
    /**
62
     * @param ServerRequestInterface $request
63
     * @throws MethodNotAllowedException
64
     * @throws RouteNotFoundException
65
     * @return Route
66
     */
67
    protected function findRouteFromRequest(ServerRequestInterface $request)
68
    {
69
        $requiredMethods = [];
70
        foreach ($this->routes as $route) {
71
            if (static::matchSchema($route, $request)
72
                && static::matchHost($route, $request)
73
                && static::matchPath($request->getUri()->getPath(), $route)
74
            ) {
75
                if (static::matchMethod($route, $request)) {
76
                    return $route;
77
                } else {
78
                    $requiredMethods = array_merge($requiredMethods, $route->getMethods());
79
                }
80
            }
81
        }
82
        if (!empty($requiredMethods)) {
83
            throw new MethodNotAllowedException($requiredMethods);
84
        }
85
        throw new RouteNotFoundException();
86
    }
87
88
    /**
89
     * @param string $path
90
     * @throws RouteNotFoundException
91
     * @return Route
92
     */
93
    protected function findRoute($path)
94
    {
95
        foreach ($this->routes as $route) {
96
            if (static::matchPath($path, $route)) {
97
                return $route;
98
            }
99
        }
100
        throw new RouteNotFoundException();
101
    }
102
103
    /**
104
     * Checks whether the route matches the current request host
105
     * @param Route $route
106
     * @param ServerRequestInterface $request
107
     * @return boolean
108
     */
109
    protected static function matchHost(Route $route, $request)
110
    {
111
        if (empty($route->getHost())) {
112
            return true;
113
        }
114
        if (preg_match($route->compile()->getHostRegex(), $request->getUri()->getHost(), $matches)) {
115
            $routeParameters = array_filter($matches, function($value, $key){
116
                return !is_int($key) && $value;
117
            }, ARRAY_FILTER_USE_BOTH);
118
            $route->setParameter('_hostMatches', $routeParameters);
119
            return true;
120
        }
121
        return false;
122
    }
123
124
    /**
125
     * Checks whether the route matches the current request method
126
     * @param Route $route
127
     * @param ServerRequestInterface $request
128
     * @return boolean
129
     */
130
    protected static function matchMethod(Route $route, $request)
131
    {
132
        if (!$route->getMethods()) {
133
            return true;
134
        }
135
        return in_array(strtoupper($request->getMethod()), $route->getMethods());
136
    }
137
138
    /**
139
     * Checks whether the route matches the scheme
140
     * @param Route $route
141
     * @param ServerRequestInterface $request
142
     * @return boolean
143
     */
144
    protected static function matchSchema(Route $route, $request)
145
    {
146
        if (!$route->getSchemes()) {
147
            return true;
148
        }
149
        return in_array($request->getUri()->getScheme(), $route->getSchemes());
150
    }
151
152
    /**
153
     * Checks whether the route matches the given path
154
     * @param string $path
155
     * @param Route $route
156
     * @return boolean
157
     */
158
    protected static function matchPath($path, Route $route)
159
    {
160
        if (preg_match($route->compile()->getPathRegex(), rawurldecode($path), $matches)) {
161
            $routeParameters = array_filter($matches, function($value, $key){
162
                return !is_int($key) && $value;
163
            }, ARRAY_FILTER_USE_BOTH);
164
            $route->setParameter('_pathMatches', $routeParameters);
165
            return true;
166
        }
167
        return false;
168
    }
169
170
    /**
171
     * Computes route parameters
172
     * @param Route $route
173
     * @return array
174
     */
175
    protected static function computeRouteParameters(Route $route)
176
    {
177
        return array_replace($route->getDefaults(),
178
            $route->getParameter('_hostMatches') ?: [],
179
            $route->getParameter('_pathMatches') ?: []
180
        );
181
    }
182
}