Completed
Push — master ( a726c1...adea8c )
by Eric
9s
created

Router::routeCollector()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 8
nc 2
nop 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Jarvis\Skill\Routing;
6
7
use FastRoute\DataGenerator\GroupCountBased as DataGenerator;
8
use FastRoute\Dispatcher\GroupCountBased as Dispatcher;
9
use FastRoute\RouteParser\Std as Parser;
10
use FastRoute\RouteCollector;
11
use Jarvis\Jarvis;
12
13
/**
14
 * @author Eric Chau <[email protected]>
15
 */
16
class Router extends Dispatcher
17
{
18
    private $computed = false;
19
    private $host = '';
20
    private $rawRoutes = [];
21
    private $routesNames = [];
22
    private $routeCollector;
23
    private $scheme = 'http';
24
25
    /**
26
     * Creates an instance of Router.
27
     *
28
     * Required to disable FastRoute\Dispatcher\GroupCountBased constructor.
29
     */
30
    public function __construct()
31
    {
32
    }
33
34
    /**
35
     * Adds a new route to the collection.
36
     *
37
     * We highly recommend you to use ::beginRoute() instead.
38
     * {@see ::beginRoute()}
39
     *
40
     * @param  Route $route
41
     * @return self
42
     */
43
    public function addRoute(Route $route): Router
44
    {
45
        $this->rawRoutes[] = [$route->method(), $route->pattern(), $route->handler()];
46
        $this->computed = false;
47
48
        if (null !== $name = $route->name()) {
49
            $this->routesNames[$name] = $route->pattern();
50
        }
51
52
        return $this;
53
    }
54
55
    /**
56
     * This is an helper that provides you a smooth syntax to add new route. Example:
57
     *
58
     * $router
59
     *     ->beginRoute('hello_world')
60
     *         ->setPattern('/hello/world')
61
     *         ->setHandler(function() {
62
     *             return 'Hello, world!';
63
     *         })
64
     *     ->end()
65
     * ;
66
     *
67
     * This syntax avoids you to create a new intance of Route, hydrating it and
68
     * then adding it to Router.
69
     *
70
     * @param  string|null $name
71
     * @return Route
72
     */
73
    public function beginRoute(string $name = null): Route
74
    {
75
        return new Route($name, $this);
76
    }
77
78
    /**
79
     * Generates and returns the full URL (with scheme and host) with provided URI.
80
     *
81
     * Notes that this method required at least the host to be setted.
82
     *
83
     * @param  string $uri
84
     * @return string
85
     */
86
    public function url(string $uri): string
87
    {
88
        $scheme = '';
89
        if ($this->host) {
90
            $uri = preg_replace('~/+~', '/', "{$this->host}$uri");
91
            $scheme = "{$this->scheme}://";
92
        }
93
94
        return "$scheme$uri";
95
    }
96
97
    /**
98
     * Returns the current scheme.
99
     *
100
     * @return string
101
     */
102
    public function scheme(): string
103
    {
104
        return $this->scheme;
105
    }
106
107
    /**
108
     * Sets the new scheme to use. Calling this method without parameter will reset
109
     * it to 'http'.
110
     *
111
     * @param string|null $scheme
112
     */
113
    public function setScheme(string $scheme = null): Router
114
    {
115
        $this->scheme = (string) $scheme ?: 'http';
116
117
        return $this;
118
    }
119
120
    /**
121
     * Returns the setted host.
122
     *
123
     * @return string
124
     */
125
    public function host(): string
126
    {
127
        return $this->host;
128
    }
129
130
    /**
131
     * Sets new host to Router. Calling this method without parameter will reset
132
     * the host to empty string.
133
     *
134
     * @param  string|null $host
135
     * @return self
136
     */
137
    public function setHost(string $host = null): Router
138
    {
139
        $this->host = (string) $host;
140
141
        return $this;
142
    }
143
144
    /**
145
     * Generates URI associated to provided route name.
146
     *
147
     * @param  string $name   The URI route name we want to generate
148
     * @param  array  $params Parameters to replace in pattern
149
     * @return string
150
     * @throws \InvalidArgumentException if provided route name is unknown
151
     */
152
    public function uri(string $name, array $params = []): string
153
    {
154
        if (!isset($this->routesNames[$name])) {
155
            throw new \InvalidArgumentException(
156
                "Cannot generate URI for '$name' cause it does not exist."
157
            );
158
        }
159
160
        $uri = $this->routesNames[$name];
161
        foreach ($params as $key => $value) {
162
            if (1 !== preg_match("~\{($key:?[^}]*)\}~", $uri, $matches)) {
163
                continue;
164
            }
165
166
            $value = (string) $value;
167
            $pieces = explode(':', $matches[1]);
168
            if (1 < count($pieces) && 1 !== preg_match("~{$pieces[1]}~", $value)) {
169
                continue;
170
            }
171
172
            $uri = str_replace($matches[0], $value, $uri);
173
        }
174
175
        return $uri;
176
    }
177
178
    /**
179
     * Alias of ::dispatch.
180
     */
181
    public function match(string $method, string $uri): array
182
    {
183
        return $this->dispatch($method, $uri);
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     * Overrides GroupCountBased::dispatch() to ensure that dispatcher always deals with up-to-date
189
     * route collection.
190
     */
191
    public function dispatch($method, $uri): array
192
    {
193
        list($this->staticRouteMap, $this->variableRouteData) = $this->routeCollector()->getData();
194
195
        return parent::dispatch(strtolower($method), $uri);
196
    }
197
198
    /**
199
     * Will always return the right RouteCollector and knows when to recompute it.
200
     *
201
     * @return RouteCollector
202
     */
203
    private function routeCollector(): RouteCollector
204
    {
205
        if (!$this->computed) {
206
            $this->routeCollector = new RouteCollector(new Parser(), new DataGenerator());
207
208
            foreach ($this->rawRoutes as $rawRoute) {
209
                list($method, $route, $handler) = $rawRoute;
210
                $this->routeCollector->addRoute($method, $route, $handler);
211
            }
212
213
            $this->computed = true;
214
        }
215
216
        return $this->routeCollector;
217
    }
218
}
219