1
|
|
|
<?php namespace Comodojo\Dispatcher\Router; |
2
|
|
|
|
3
|
|
|
use \Comodojo\Foundation\Base\Configuration; |
4
|
|
|
use \Comodojo\Dispatcher\Components\AbstractModel; |
5
|
|
|
use \Comodojo\Dispatcher\Cache\RouterCache; |
6
|
|
|
use \Comodojo\Dispatcher\Request\Model as Request; |
7
|
|
|
use \Comodojo\Dispatcher\Router\Parser; |
8
|
|
|
use \Comodojo\Dispatcher\Router\Route; |
9
|
|
|
use \Comodojo\Dispatcher\Router\Model as Router; |
10
|
|
|
use \Comodojo\SimpleCache\Manager as SimpleCacheManager; |
11
|
|
|
use \Comodojo\Exception\DispatcherException; |
12
|
|
|
use \Psr\Log\LoggerInterface; |
13
|
|
|
use \Countable; |
14
|
|
|
use \Exception; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @package Comodojo Dispatcher |
18
|
|
|
* @author Marco Giovinazzi <[email protected]> |
19
|
|
|
* @author Marco Castiello <[email protected]> |
20
|
|
|
* @license MIT |
21
|
|
|
* |
22
|
|
|
* LICENSE: |
23
|
|
|
* |
24
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
25
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
26
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
27
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
28
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
29
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
30
|
|
|
* THE SOFTWARE. |
31
|
|
|
*/ |
32
|
|
|
|
33
|
|
|
class Table extends AbstractModel implements Countable { |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Types of route that this table will accept |
37
|
|
|
* @var array |
38
|
|
|
*/ |
39
|
|
|
const ALLOWED_ROUTES = [ "ROUTE", "REDIRECT", "ERROR" ]; |
|
|
|
|
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Current repository of routes |
43
|
|
|
* @var array |
44
|
|
|
*/ |
45
|
|
|
protected $routes = []; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var Parser |
49
|
|
|
*/ |
50
|
|
|
protected $parser; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var RouterCache |
54
|
|
|
*/ |
55
|
|
|
protected $cache; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Table constructor |
59
|
|
|
* |
60
|
|
|
* @param Configuration $configuration |
61
|
|
|
* @param LoggerInterface $logger |
62
|
|
|
* @param SimpleCacheManager $cache |
63
|
|
|
*/ |
64
|
1 |
|
public function __construct( |
65
|
|
|
Configuration $configuration, |
66
|
|
|
LoggerInterface $logger, |
67
|
|
|
SimpleCacheManager $cache |
68
|
|
|
) { |
69
|
|
|
|
70
|
1 |
|
parent::__construct($configuration, $logger); |
71
|
|
|
|
72
|
1 |
|
$this->parser = new Parser($logger); |
73
|
1 |
|
$this->cache = new RouterCache($cache); |
74
|
|
|
|
75
|
1 |
|
$this->readCache(); |
76
|
|
|
|
77
|
1 |
|
} |
78
|
|
|
|
79
|
1 |
|
public function add( |
80
|
|
|
$route, |
81
|
|
|
$type, |
82
|
|
|
$class = null, |
83
|
|
|
array $parameters = [] |
84
|
|
|
) { |
85
|
|
|
|
86
|
1 |
|
$type = strtoupper($type); |
87
|
|
|
|
88
|
1 |
|
if ( !in_array($type, self::ALLOWED_ROUTES) ) { |
89
|
|
|
|
90
|
|
|
$this->getLogger()->error("Invalid route definition - unknown type $type for route $route)"); |
|
|
|
|
91
|
|
|
|
92
|
1 |
|
} else if ( $type == 'ROUTE' && empty($class) ) { |
|
|
|
|
93
|
|
|
|
94
|
|
|
$this->getLogger()->error("Invalid route definition - missing class for route $route)"); |
|
|
|
|
95
|
|
|
|
96
|
|
|
} else { |
97
|
|
|
|
98
|
1 |
|
$routeData = $this->get($route); |
99
|
|
|
|
100
|
1 |
|
if ( !is_null($routeData) ) { |
101
|
|
|
|
102
|
|
|
$this->updateRoute($routeData, $type, $class, $parameters); |
103
|
|
|
|
104
|
|
|
} else { |
105
|
|
|
|
106
|
1 |
|
$folders = explode("/", $route); |
|
|
|
|
107
|
|
|
|
108
|
1 |
|
$this->registerRoute($folders, $type, $class, $parameters); |
109
|
|
|
|
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
} |
113
|
|
|
|
114
|
1 |
|
return $this; |
115
|
|
|
|
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Get registered routes count |
120
|
|
|
* |
121
|
|
|
* @return int |
122
|
|
|
*/ |
123
|
|
|
public function count() { |
124
|
|
|
|
125
|
|
|
return count($this->routes); |
126
|
|
|
|
127
|
|
|
} |
128
|
|
|
|
129
|
1 |
|
public function getRoutes() { |
130
|
|
|
|
131
|
1 |
|
return $this->routes; |
132
|
|
|
|
133
|
|
|
} |
134
|
|
|
|
135
|
1 |
|
public function get($route) { |
136
|
|
|
|
137
|
1 |
|
$regex = $this->regex($route); |
138
|
|
|
|
139
|
1 |
|
if (isset($this->routes[$regex])) { |
140
|
1 |
|
return $this->routes[$regex]; |
141
|
|
|
} |
142
|
|
|
|
143
|
1 |
|
return null; |
144
|
|
|
|
145
|
|
|
} |
146
|
|
|
|
147
|
1 |
|
public function regex($route) { |
148
|
|
|
|
149
|
1 |
|
$folders = explode("/", $route); |
|
|
|
|
150
|
|
|
|
151
|
1 |
|
return $this->parser->read($folders); |
152
|
|
|
|
153
|
|
|
} |
154
|
|
|
|
155
|
1 |
|
public function remove($route) { |
156
|
|
|
|
157
|
1 |
|
$regex = $this->regex($route); |
158
|
|
|
|
159
|
1 |
|
$routes = $this->routes; |
160
|
|
|
|
161
|
1 |
|
if (isset($routes[$regex])) { |
162
|
|
|
|
163
|
1 |
|
unset($routes[$regex]); |
164
|
|
|
|
165
|
1 |
|
$this->routes = $routes; |
166
|
|
|
|
167
|
1 |
|
return true; |
168
|
|
|
|
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return false; |
172
|
|
|
|
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
public function defaultRoute() { |
176
|
|
|
|
177
|
|
|
return $this->get('/'); |
178
|
|
|
|
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
public function load(array $routes) { |
182
|
|
|
|
183
|
|
|
if (!empty($routes)) { |
184
|
|
|
|
185
|
|
|
foreach ($routes as $name => $route) { |
186
|
|
|
|
187
|
|
|
$this->add( |
188
|
|
|
$route['route'], |
189
|
|
|
$route['type'], |
190
|
|
|
empty($route['class']) ? null : $route['class'], |
191
|
|
|
empty($route['parameters']) ? [] : $route['parameters'] |
192
|
|
|
); |
193
|
|
|
|
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
$count = $this->count(); |
199
|
|
|
$this->logger->debug("$count routes loaded in routing table"); |
|
|
|
|
200
|
|
|
|
201
|
|
|
$this->dumpCache(); |
202
|
|
|
|
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
// This method add a route to the supported list |
206
|
1 |
|
private function registerRoute($folders, $type, $class = null, array $parameters = []) { |
207
|
|
|
|
208
|
|
|
// The values associated with a route are as follows: |
209
|
|
|
// $route = new Route($this->router); |
|
|
|
|
210
|
1 |
|
$route = new Route(); |
211
|
|
|
|
212
|
1 |
|
$this->updateRoute($route, $type, $class, $parameters); |
213
|
|
|
|
214
|
|
|
// $route->setType($type) // Type of route |
|
|
|
|
215
|
|
|
// ->setClassName($class) // Class to be invoked |
|
|
|
|
216
|
|
|
// ->setParameters($parameters); // Parameters passed via the composer.json configuration (cache, ttl, etc...) |
|
|
|
|
217
|
|
|
|
218
|
1 |
|
$this->logger->debug("Route table - route: ".implode("/", $folders)); |
|
|
|
|
219
|
|
|
|
220
|
|
|
// This method generate a global regular expression which will be able to match all the URI supported by the route |
221
|
1 |
|
$regex = $this->parser->read($folders, $route); |
222
|
|
|
|
223
|
1 |
|
$this->logger->debug("Route table - route regex: $regex"); |
|
|
|
|
224
|
|
|
|
225
|
1 |
|
$this->routes = array_merge($this->routes, [$regex => $route]); |
226
|
|
|
|
227
|
1 |
|
} |
228
|
|
|
|
229
|
1 |
|
private function updateRoute(Route $route, $type, $class = null, array $parameters = []) { |
230
|
|
|
|
231
|
1 |
|
$route->setType($type) |
232
|
1 |
|
->setClassName($class) |
233
|
1 |
|
->setParameters($parameters); |
234
|
|
|
|
235
|
1 |
|
if ( !empty($parameters['redirect-code']) ) $route->setRedirectCode($parameters['redirect-code']); |
236
|
1 |
|
if ( !empty($parameters['redirect-location']) ) $route->setRedirectLocation($parameters['redirect-location']); |
237
|
1 |
|
if ( !empty($parameters['redirect-message']) ) $route->setRedirectLocation($parameters['redirect-message']); |
238
|
1 |
|
if ( !empty($parameters['redirect-type']) ) $route->setRedirectType($parameters['redirect-type']); |
239
|
|
|
|
240
|
1 |
|
if ( !empty($parameters['error-code']) ) $route->setErrorCode($parameters['error-code']); |
241
|
1 |
|
if ( !empty($parameters['error-message']) ) $route->setErrorMessage($parameters['error-message']); |
242
|
|
|
|
243
|
1 |
|
} |
244
|
|
|
|
245
|
1 |
|
private function readCache() { |
246
|
|
|
|
247
|
1 |
|
if ($this->configuration->get('routing-table-cache') !== true) return; |
248
|
|
|
|
249
|
|
|
$data = $this->cache->read(); |
250
|
|
|
|
251
|
|
|
if (is_null($data)) { |
|
|
|
|
252
|
|
|
|
253
|
|
|
$this->routes = []; |
254
|
|
|
|
255
|
|
|
return; |
256
|
|
|
|
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$this->routes = $data; |
260
|
|
|
|
261
|
|
|
$this->logger->debug("Routing table loaded from cache"); |
|
|
|
|
262
|
|
|
|
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
private function dumpCache() { |
266
|
|
|
|
267
|
|
|
if ($this->configuration->get('routing-table-cache') !== true) return; |
268
|
|
|
|
269
|
|
|
$ttl = $this->configuration->get('routing-table-ttl'); |
270
|
|
|
|
271
|
|
|
if ($this->cache->dump($this->routes, $ttl)) { |
272
|
|
|
$this->logger->debug("Routing table saved to cache"); |
|
|
|
|
273
|
|
|
} else { |
274
|
|
|
$this->logger->warning("Cannot save routing table to cache"); |
|
|
|
|
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
} |
280
|
|
|
|
PHP provides two ways to mark string literals. Either with single quotes
'literal'
or with double quotes"literal"
. The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (
\'
) and the backslash (\\
). Every other character is displayed as is.Double quoted string literals may contain other variables or more complex escape sequences.
will print an indented:
Single is Value
If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.
For more information on PHP string literals and available escape sequences see the PHP core documentation.