Completed
Push — master ( 62539f...2a1b27 )
by Riikka
02:22
created

Router::getDynamicRouteIds()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 1
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 2
rs 9.7666
c 0
b 0
f 0
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 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 18
    public function __construct(RouteDefinitionProvider $provider)
27
    {
28 18
        $this->provider = $provider;
29 18
        $this->allowedMethods = [];
30 18
    }
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 18
    public function route(string $method, string $path): Route
41
    {
42 18
        if (!HttpMethod::isValid($method)) {
43 1
            throw new \InvalidArgumentException("Invalid HTTP method '$method'");
44
        }
45
46 17
        $this->allowedMethods = [];
47
48 17
        $segments = split_segments($path);
49 17
        $routes = $this->matchRoutes($method, $segments);
50
51 17
        if (\count($routes) === 1) {
52 12
            return $routes[0];
53
        }
54
55 5
        if (\count($routes) > 1) {
56 1
            throw new \UnexpectedValueException("The given path '$path' matches more than one route");
57
        }
58
59 4
        if (\count($this->allowedMethods) > 0) {
60 1
            if (\in_array(HttpMethod::GET, $this->allowedMethods, true)) {
61 1
                $this->allowedMethods[] = HttpMethod::HEAD;
62
            }
63
64 1
            throw new MethodNotAllowedException(
65 1
                "The requested method '$method' is not within list of allowed methods",
66 1
                array_unique($this->allowedMethods)
67
            );
68
        }
69
70 3
        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 17
    private function matchRoutes(string $method, array $segments): array
80
    {
81 17
        $staticIds = $this->provider->getRoutesByStaticPath(implode('/', $segments));
82
83 17
        if ($staticIds !== []) {
84 8
            $routes = $this->getMatchingRoutes($staticIds, $method, $segments);
85
86 8
            if ($routes !== []) {
87 7
                return $routes;
88
            }
89
        }
90
91 11
        $matchedIds = $this->getDynamicRouteIds($segments);
92 11
        return $this->getMatchingRoutes($matchedIds, $method, $segments);
93
    }
94
95
    /**
96
     * Returns a list of route ids for dynamic routes that have matching static path segments.
97
     * @param string[] $segments The requested path segments
98
     * @return int[] List of route ids for dynamic routes that have matching static path segments
99
     */
100 11
    private function getDynamicRouteIds(array $segments): array
101
    {
102 11
        $matched = [];
103 11
        $index = 0;
104
105
        do {
106 11
            $matched[] =
107 11
                $this->provider->getRoutesBySegmentValue($index, $segments[$index]) +
108 11
                $this->provider->getRoutesBySegmentValue($index, RouteDefinition::DYNAMIC_SEGMENT);
109 11
            $index++;
110 11
        } while (isset($segments[$index]));
111
112 11
        $matched[] = $this->provider->getRoutesBySegmentCount(\count($segments));
113
114 11
        return array_values(array_intersect_key(... $matched));
115
    }
116
117
    /**
118
     * Returns the routes for the given ids that match the requested method and segments.
119
     * @param int[] $ids List of route ids to match
120
     * @param string $method The HTTP request method
121
     * @param string[] $segments The requested path segments
122
     * @return Route[] List of matching routes
123
     */
124 17
    private function getMatchingRoutes(array $ids, string $method, array $segments): array
125
    {
126 17
        $routes = [];
127
128 17
        foreach ($ids as $id) {
129 15
            $definition = $this->provider->getRouteDefinition($id);
130 15
            $values = [];
131
132 15
            if (!$definition->matchPatterns($segments, $values)) {
133 2
                continue;
134
            }
135
136 14
            if (!$definition->isMethodAllowed($method)) {
137 3
                array_push($this->allowedMethods, ... $definition->getMethods());
138 3
                continue;
139
            }
140
141 13
            $routes[] = new Route($definition, $method, $segments, $values);
142
        }
143
144 17
        return $routes;
145
    }
146
147
    /**
148
     * Returns the encoded URL for the route with the given name.
149
     * @param string $name Name of the route
150
     * @param string[] $parameters Values for the route parameters
151
     * @return string The encoded URL for the route
152
     */
153 9
    public function generateUrl(string $name, array $parameters = []): string
154
    {
155 9
        $definition = $this->provider->getRouteDefinitionByName($name);
156 9
        return $definition->formatUrl($parameters);
157
    }
158
}
159