Router   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 53
dl 0
loc 147
ccs 56
cts 56
cp 1
rs 10
c 0
b 0
f 0
wmc 18

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A route() 0 31 6
A matchRoutes() 0 18 4
A generateUrl() 0 4 1
A getDynamicRouteIds() 0 15 2
A getMatchingRoutes() 0 21 4
1
<?php
2
3
namespace Simply\Router;
4
5
use Simply\Router\Exception\MethodNotAllowedException;
6
use Simply\Router\Exception\RouteNotFoundException;
7
8
/**
9
 * Class for routing requested methods and paths to specific routes.
10
 * @author Riikka Kalliomäki <[email protected]>
11
 * @copyright Copyright (c) 2018-2019 Riikka Kalliomäki
12
 * @license http://opensource.org/licenses/mit-license.php MIT License
13
 */
14
class Router
15
{
16
    /** @var RouteDefinitionProvider The route definition provider */
17
    private $provider;
18
19
    /** @var string[] List of methods that would be allowed for the routed path */
20
    private $allowedMethods;
21
22
    /**
23
     * Router constructor.
24
     * @param RouteDefinitionProvider $provider The route definition provider
25
     */
26 24
    public function __construct(RouteDefinitionProvider $provider)
27
    {
28 24
        $this->provider = $provider;
29 24
        $this->allowedMethods = [];
30 24
    }
31
32
    /**
33
     * Routes the given path with the given HTTP request method to a specific route.
34
     * @param string $method The HTTP request method
35
     * @param string $path The decoded request path
36
     * @return Route Matching route
37
     * @throws MethodNotAllowedException If the path matches at least one route, but the method is not allowed
38
     * @throws RouteNotFoundException If no route matches the given path
39
     */
40 24
    public function route(string $method, string $path): Route
41
    {
42 24
        if (!HttpMethod::isValid($method)) {
43 1
            throw new \InvalidArgumentException("Invalid HTTP method '$method'");
44
        }
45
46 23
        $this->allowedMethods = [];
47
48 23
        $segments = split_segments($path);
49 23
        $routes = $this->matchRoutes($method, $segments);
50
51 23
        if (\count($routes) === 1) {
52 16
            return $routes[0];
53
        }
54
55 8
        if (\count($routes) !== 0) {
56 1
            throw new \UnexpectedValueException("The given path '$path' matches more than one route");
57
        }
58
59 7
        if (\count($this->allowedMethods) > 0) {
60 2
            if (\in_array(HttpMethod::GET, $this->allowedMethods, true)) {
61 1
                $this->allowedMethods[] = HttpMethod::HEAD;
62
            }
63
64 2
            throw new MethodNotAllowedException(
65 2
                "The requested method '$method' is not within list of allowed methods",
66 2
                array_values(array_intersect(HttpMethod::getAll(), $this->allowedMethods))
67
            );
68
        }
69
70 5
        throw new RouteNotFoundException("The given path '$path' did not match any defined route");
71
    }
72
73
    /**
74
     * Returns routes that match the given HTTP request method and path segments.
75
     * @param string $method The HTTP request method
76
     * @param string[] $segments The requested path segments
77
     * @return Route[] List of matching routes
78
     */
79 23
    private function matchRoutes(string $method, array $segments): array
80
    {
81 23
        $staticIds = $this->provider->getRoutesByStaticPath(implode('/', $segments));
82
83 23
        if ($staticIds !== []) {
84 12
            $routes = $this->getMatchingRoutes($staticIds, $method, $segments);
85
86 12
            if ($routes !== []) {
87 10
                return $routes;
88
            }
89
        }
90
91 15
        if ($segments === []) {
92 1
            return [];
93
        }
94
95 14
        $matchedIds = $this->getDynamicRouteIds($segments);
96 14
        return $this->getMatchingRoutes($matchedIds, $method, $segments);
97
    }
98
99
    /**
100
     * Returns a list of route ids for dynamic routes that have matching static path segments.
101
     * @param string[] $segments The requested path segments
102
     * @return int[] List of route ids for dynamic routes that have matching static path segments
103
     */
104 14
    private function getDynamicRouteIds(array $segments): array
105
    {
106 14
        $matched = [];
107 14
        $index = 0;
108
109
        do {
110 14
            $matched[] =
111 14
                $this->provider->getRoutesBySegmentValue($index, $segments[$index]) +
112 14
                $this->provider->getRoutesBySegmentValue($index, RouteDefinition::DYNAMIC_SEGMENT);
113 14
            $index++;
114 14
        } while (isset($segments[$index]));
115
116 14
        $matched[] = $this->provider->getRoutesBySegmentCount(\count($segments));
117
118 14
        return array_values(array_intersect_key(... $matched));
119
    }
120
121
    /**
122
     * Returns the routes for the given ids that match the requested method and segments.
123
     * @param int[] $ids List of route ids to match
124
     * @param string $method The HTTP request method
125
     * @param string[] $segments The requested path segments
126
     * @return Route[] List of matching routes
127
     */
128 22
    private function getMatchingRoutes(array $ids, string $method, array $segments): array
129
    {
130 22
        $routes = [];
131
132 22
        foreach ($ids as $id) {
133 20
            $definition = $this->provider->getRouteDefinition($id);
134 20
            $values = [];
135
136 20
            if (!$definition->matchPatterns($segments, $values)) {
137 3
                continue;
138
            }
139
140 19
            if (!$definition->isMethodAllowed($method)) {
141 4
                array_push($this->allowedMethods, ... $definition->getMethods());
142 4
                continue;
143
            }
144
145 17
            $routes[] = new Route($definition, $method, $segments, $values);
146
        }
147
148 22
        return $routes;
149
    }
150
151
    /**
152
     * Returns the encoded URL for the route with the given name.
153
     * @param string $name Name of the route
154
     * @param string[] $parameters Values for the route parameters
155
     * @return string The encoded URL for the route
156
     */
157 13
    public function generateUrl(string $name, array $parameters = []): string
158
    {
159 13
        $definition = $this->provider->getRouteDefinitionByName($name);
160 13
        return $definition->formatUrl($parameters);
161
    }
162
}
163