1
|
|
|
<?php namespace Comodojo\Dispatcher; |
2
|
|
|
|
3
|
|
|
use \Comodojo\Dispatcher\Components\AbstractModel; |
4
|
|
|
use \Comodojo\Dispatcher\Components\DefaultConfiguration; |
5
|
|
|
use \Comodojo\Dispatcher\Cache\ServerCache; |
6
|
|
|
use \Comodojo\Dispatcher\Request\Model as Request; |
7
|
|
|
use \Comodojo\Dispatcher\Router\Model as Router; |
8
|
|
|
use \Comodojo\Dispatcher\Router\Route; |
9
|
|
|
use \Comodojo\Dispatcher\Response\Model as Response; |
10
|
|
|
use \Comodojo\Dispatcher\Extra\Model as Extra; |
11
|
|
|
use \Comodojo\Dispatcher\Output\Processor; |
12
|
|
|
use \Comodojo\Dispatcher\Output\Redirect; |
13
|
|
|
use \Comodojo\Dispatcher\Output\Error; |
14
|
|
|
use \Comodojo\Dispatcher\Events\DispatcherEvent; |
15
|
|
|
use \Comodojo\Dispatcher\Events\ServiceEvent; |
16
|
|
|
use \Comodojo\Dispatcher\Traits\CacheTrait; |
17
|
|
|
use \Comodojo\Dispatcher\Traits\ServerCacheTrait; |
18
|
|
|
use \Comodojo\Dispatcher\Traits\RequestTrait; |
19
|
|
|
use \Comodojo\Dispatcher\Traits\ResponseTrait; |
20
|
|
|
use \Comodojo\Dispatcher\Traits\RouterTrait; |
21
|
|
|
use \Comodojo\Dispatcher\Traits\RouteTrait; |
22
|
|
|
use \Comodojo\Dispatcher\Traits\ExtraTrait; |
23
|
|
|
use \Comodojo\Foundation\Events\EventsTrait; |
24
|
|
|
use \Comodojo\Foundation\Base\Configuration; |
25
|
|
|
use \Comodojo\Foundation\Timing\TimingTrait; |
26
|
|
|
use \Comodojo\Foundation\Events\Manager as EventsManager; |
27
|
|
|
use \Comodojo\Foundation\Logging\Manager as LogManager; |
28
|
|
|
use \Comodojo\SimpleCache\Manager as SimpleCacheManager; |
29
|
|
|
use \Psr\Log\LoggerInterface; |
30
|
|
|
use \Comodojo\Exception\DispatcherException; |
31
|
|
|
use \Exception; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @package Comodojo Dispatcher |
35
|
|
|
* @author Marco Giovinazzi <[email protected]> |
36
|
|
|
* @author Marco Castiello <[email protected]> |
37
|
|
|
* @license MIT |
38
|
|
|
* |
39
|
|
|
* LICENSE: |
40
|
|
|
* |
41
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
42
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
43
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
44
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
45
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
46
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
47
|
|
|
* THE SOFTWARE. |
48
|
|
|
*/ |
49
|
|
|
|
50
|
|
|
class Dispatcher extends AbstractModel { |
51
|
|
|
|
52
|
|
|
use TimingTrait; |
53
|
|
|
use CacheTrait; |
54
|
|
|
use ServerCacheTrait; |
55
|
|
|
use RequestTrait; |
56
|
|
|
use ResponseTrait; |
57
|
|
|
use RouterTrait; |
58
|
|
|
use RouteTrait; |
59
|
|
|
use ExtraTrait; |
60
|
|
|
use EventsTrait; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* The main dispatcher constructor. |
64
|
|
|
*/ |
65
|
1 |
|
public function __construct( |
66
|
|
|
array $configuration = [], |
67
|
|
|
EventsManager $events = null, |
68
|
|
|
SimpleCacheManager $cache = null, |
69
|
|
|
LoggerInterface $logger = null |
70
|
|
|
) { |
71
|
|
|
|
72
|
|
|
// starting output buffer |
73
|
1 |
|
ob_start(); |
74
|
|
|
|
75
|
|
|
// fix current timestamp |
76
|
1 |
|
$this->setTiming(); |
77
|
|
|
|
78
|
|
|
// init core components |
79
|
|
|
// create new configuration object and merge configuration |
80
|
1 |
|
$configuration_object = new Configuration(DefaultConfiguration::get()); |
81
|
1 |
|
$configuration_object->merge($configuration); |
82
|
|
|
|
83
|
1 |
|
$logger = is_null($logger) ? LogManager::createFromConfiguration($configuration_object)->getLogger() : $logger; |
84
|
|
|
|
85
|
1 |
|
parent::__construct($configuration_object, $logger); |
86
|
|
|
|
87
|
1 |
|
$this->logger->debug("--------------------------------------------------------"); |
|
|
|
|
88
|
1 |
|
$this->logger->debug("Dispatcher run-cycle starts at ".$this->getTime()->format('c')); |
|
|
|
|
89
|
|
|
|
90
|
|
|
try { |
91
|
|
|
|
92
|
|
|
// init other components |
93
|
1 |
|
$this->setEvents(is_null($events) ? EventsManager::create($this->logger) : $events); |
94
|
1 |
|
$this->setCache(is_null($cache) ? SimpleCacheManager::createFromConfiguration($this->configuration, $this->logger) : $cache); |
95
|
1 |
|
$this->setServerCache(new ServerCache($this->getCache())); |
96
|
|
|
|
97
|
|
|
// init models |
98
|
1 |
|
$this->setExtra(new Extra($this->logger)); |
|
|
|
|
99
|
1 |
|
$this->setRequest(new Request($this->configuration, $this->logger)); |
100
|
1 |
|
$this->setRouter(new Router($this->configuration, $this->logger, $this->cache, $this->events, $this->extra)); |
101
|
1 |
|
$this->setResponse(new Response($this->configuration, $this->logger)); |
102
|
|
|
|
103
|
1 |
|
} catch (Exception $e) { |
104
|
|
|
|
105
|
1 |
|
$this->logger->critical($e->getMessage(), $e->getTrace()); |
106
|
|
|
|
107
|
|
|
throw $e; |
108
|
|
|
|
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
// we're ready! |
112
|
1 |
|
$this->logger->debug("Dispatcher ready"); |
|
|
|
|
113
|
|
|
|
114
|
1 |
|
} |
115
|
|
|
|
116
|
1 |
|
public function dispatch() { |
117
|
|
|
|
118
|
1 |
|
$configuration = $this->getConfiguration(); |
119
|
1 |
|
$logger = $this->getLogger(); |
120
|
1 |
|
$events = $this->getEvents(); |
121
|
1 |
|
$request = $this->getRequest(); |
|
|
|
|
122
|
1 |
|
$response = $this->getResponse(); |
123
|
|
|
|
124
|
1 |
|
$logger->debug("Starting to dispatch."); |
|
|
|
|
125
|
1 |
|
$logger->debug("Emitting global dispatcher event."); |
|
|
|
|
126
|
1 |
|
$events->emit(new DispatcherEvent($this)); |
127
|
|
|
|
128
|
|
|
// if dispatcher is administratively disabled, halt the process immediately |
129
|
1 |
|
if ($configuration->get('enabled') === false) { |
130
|
|
|
|
131
|
|
|
$logger->debug("Dispatcher disabled, shutting down gracefully."); |
|
|
|
|
132
|
|
|
|
133
|
|
|
$status = $configuration->get('disabled-status'); |
134
|
|
|
$content = $configuration->get('disabled-message'); |
135
|
|
|
|
136
|
|
|
$response |
137
|
|
|
->getStatus() |
138
|
|
|
->set($status === null ? 503 : $status); |
139
|
|
|
$response |
140
|
|
|
->getContent() |
141
|
|
|
->set($content === null ? 'Service unavailable (dispatcher disabled)' : $content); |
142
|
|
|
|
143
|
|
|
return $this->shutdown(); |
144
|
|
|
|
145
|
|
|
} |
146
|
|
|
|
147
|
1 |
|
$cache = $this->getServerCache(); |
148
|
|
|
|
149
|
1 |
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.request')); |
150
|
1 |
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.request.'.$this->request->getMethod()->get())); |
151
|
1 |
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.request.#')); |
152
|
|
|
|
153
|
1 |
|
$logger->debug("Starting router"); |
|
|
|
|
154
|
|
|
|
155
|
|
|
try { |
156
|
|
|
|
157
|
1 |
|
$route = $this->getRouter() |
158
|
1 |
|
->route($this->request); |
159
|
|
|
$this->setRoute($route); |
160
|
|
|
|
161
|
1 |
|
} catch (DispatcherException $de) { |
162
|
|
|
|
163
|
1 |
|
$logger->debug("Route error (".$de->getStatus()."), shutting down dispatcher"); |
|
|
|
|
164
|
1 |
|
$this->processDispatcherException($de); |
165
|
1 |
|
return $this->shutdown(); |
166
|
|
|
|
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
$route_type = $route->getType(); |
170
|
|
|
$route_service = $route->getServiceName(); |
171
|
|
|
|
172
|
|
|
$logger->debug("Route acquired, type $route_type"); |
|
|
|
|
173
|
|
|
|
174
|
|
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.route')); |
175
|
|
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.route.'.$route_type)); |
176
|
|
|
|
177
|
|
|
if ( $route_type === 'ROUTE' ) { |
178
|
|
|
$logger->debug("Route leads to service $route_service"); |
|
|
|
|
179
|
|
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.route.'.$route_service)); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.route.#')); |
183
|
|
|
|
184
|
|
|
if ($cache->read($this->request, $this->response)) { |
185
|
|
|
// we have a cache! |
186
|
|
|
// shutdown immediately |
187
|
|
|
return $this->shutdown(); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
try { |
191
|
|
|
|
192
|
|
|
switch ($route_type) { |
193
|
|
|
|
194
|
|
|
case 'ROUTE': |
|
|
|
|
195
|
|
|
|
196
|
|
|
$logger->debug("Running $route_service service"); |
|
|
|
|
197
|
|
|
|
198
|
|
|
// translate route to service |
199
|
|
|
$this->router->compose($this->response); |
200
|
|
|
|
201
|
|
|
$this->processConfigurationParameters($this->route); |
202
|
|
|
|
203
|
|
|
$cache->dump($this->request, $this->response, $this->route); |
204
|
|
|
|
205
|
|
|
break; |
206
|
|
|
|
207
|
|
|
case 'REDIRECT': |
|
|
|
|
208
|
|
|
|
209
|
|
|
$logger->debug("Redirecting response..."); |
|
|
|
|
210
|
|
|
|
211
|
|
|
Redirect::compose( |
212
|
|
|
$this->getRequest(), |
213
|
1 |
|
$this->getResponse(), |
214
|
|
|
$this->getRoute() |
215
|
|
|
); |
216
|
|
|
|
217
|
|
|
break; |
218
|
|
|
|
219
|
|
|
case 'ERROR': |
|
|
|
|
220
|
|
|
|
221
|
|
|
$logger->debug("Sending error message..."); |
|
|
|
|
222
|
|
|
|
223
|
|
|
Error::compose($this->route); |
224
|
|
|
|
225
|
1 |
|
break; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
} catch (DispatcherException $de) { |
229
|
|
|
|
230
|
|
|
$logger->debug("Service exception (".$de->getStatus()."), shutting down dispatcher"); |
|
|
|
|
231
|
|
|
$this->processDispatcherException($de); |
232
|
|
|
|
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return $this->shutdown(); |
236
|
|
|
|
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
private function processConfigurationParameters($route) { |
240
|
|
|
|
241
|
|
|
$params = $route->getParameter('headers'); |
242
|
|
|
|
243
|
|
|
if ( !empty($params) && is_array($params) ) { |
244
|
|
|
foreach($params as $name => $value) { |
245
|
|
|
$this->getResponse()->getHeaders()->set($name, $value); |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
} |
250
|
|
|
|
251
|
1 |
|
private function processDispatcherException(DispatcherException $de) { |
252
|
|
|
|
253
|
1 |
|
$status = $de->getStatus(); |
254
|
1 |
|
$message = $de->getMessage(); |
255
|
1 |
|
$headers = $de->getHeaders(); |
256
|
|
|
|
257
|
1 |
|
$response = $this->getResponse(); |
258
|
|
|
|
259
|
1 |
|
$response->getStatus()->set($status); |
260
|
1 |
|
$response->getContent()->set($message); |
261
|
1 |
|
$response->getHeaders()->merge($headers); |
262
|
|
|
|
263
|
1 |
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* @param string $name |
267
|
|
|
*/ |
268
|
1 |
|
private function createServiceSpecializedEvents($name) { |
269
|
|
|
|
270
|
1 |
|
$this->logger->debug("Emitting $name service-event"); |
|
|
|
|
271
|
|
|
|
272
|
1 |
|
return new ServiceEvent( |
273
|
1 |
|
$name, |
274
|
1 |
|
$this->getConfiguration(), |
275
|
1 |
|
$this->getLogger(), |
276
|
1 |
|
$this->getCache(), |
277
|
1 |
|
$this->getServerCache(), |
278
|
1 |
|
$this->getEvents(), |
279
|
1 |
|
$this->getRequest(), |
280
|
1 |
|
$this->getRouter(), |
281
|
1 |
|
$this->getResponse(), |
282
|
1 |
|
$this->getExtra() |
283
|
1 |
|
); |
284
|
|
|
|
285
|
|
|
} |
286
|
|
|
|
287
|
1 |
|
private function shutdown() { |
288
|
|
|
|
289
|
1 |
|
$configuration = $this->getConfiguration(); |
290
|
1 |
|
$logger = $this->getLogger(); |
291
|
1 |
|
$events = $this->getEvents(); |
292
|
1 |
|
$request = $this->getRequest(); |
293
|
1 |
|
$response = $this->getResponse(); |
294
|
|
|
|
295
|
1 |
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.response')); |
296
|
1 |
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.response.'.$response->getStatus()->get())); |
297
|
1 |
|
$events->emit($this->createServiceSpecializedEvents('dispatcher.response.#')); |
298
|
|
|
|
299
|
1 |
|
$response->consolidate($request, $this->route); |
300
|
|
|
|
301
|
1 |
|
$logger->debug("Composing return value"); |
|
|
|
|
302
|
|
|
|
303
|
1 |
|
$return = Processor::parse( |
304
|
1 |
|
$configuration, |
305
|
1 |
|
$logger, |
306
|
1 |
|
$request, |
307
|
|
|
$response |
308
|
1 |
|
); |
309
|
|
|
|
310
|
1 |
|
$logger->debug("Dispatcher run-cycle ends"); |
|
|
|
|
311
|
|
|
|
312
|
|
|
// This could cause WSOD with some PHP-FPM configurations |
313
|
|
|
// if ( function_exists('fastcgi_finish_request') ) fastcgi_finish_request(); |
|
|
|
|
314
|
|
|
// else ob_end_clean(); |
|
|
|
|
315
|
1 |
|
ob_end_clean(); |
316
|
|
|
|
317
|
1 |
|
return $return; |
318
|
|
|
|
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
} |
322
|
|
|
|
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.