|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Bluz Framework Component |
|
4
|
|
|
* |
|
5
|
|
|
* @copyright Bluz PHP Team |
|
6
|
|
|
* @link https://github.com/bluzphp/framework |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* @namespace |
|
11
|
|
|
*/ |
|
12
|
|
|
namespace Bluz\Application; |
|
13
|
|
|
|
|
14
|
|
|
use Bluz\Application\Exception\ApplicationException; |
|
15
|
|
|
use Bluz\Application\Exception\ForbiddenException; |
|
16
|
|
|
use Bluz\Application\Exception\NotAcceptableException; |
|
17
|
|
|
use Bluz\Application\Exception\NotAllowedException; |
|
18
|
|
|
use Bluz\Application\Exception\RedirectException; |
|
19
|
|
|
use Bluz\Application\Exception\ReloadException; |
|
20
|
|
|
use Bluz\Auth\AbstractRowEntity; |
|
21
|
|
|
use Bluz\Common; |
|
22
|
|
|
use Bluz\Controller\Reflection; |
|
23
|
|
|
use Bluz\Proxy\Acl; |
|
24
|
|
|
use Bluz\Proxy\Cache; |
|
25
|
|
|
use Bluz\Proxy\Config; |
|
26
|
|
|
use Bluz\Proxy\Layout; |
|
27
|
|
|
use Bluz\Proxy\Logger; |
|
28
|
|
|
use Bluz\Proxy\Messages; |
|
29
|
|
|
use Bluz\Proxy\Request; |
|
30
|
|
|
use Bluz\Proxy\Response; |
|
31
|
|
|
use Bluz\Proxy\Router; |
|
32
|
|
|
use Bluz\Proxy\Session; |
|
33
|
|
|
use Bluz\Proxy\Translator; |
|
34
|
|
|
use Bluz\Request\RequestFactory; |
|
35
|
|
|
use Bluz\Response\Response as ResponseInstance; |
|
36
|
|
|
use Bluz\View\View; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Application |
|
40
|
|
|
* |
|
41
|
|
|
* @package Bluz\Application |
|
42
|
|
|
* @link https://github.com/bluzphp/framework/wiki/Application * |
|
43
|
|
|
* @author Anton Shevchuk |
|
44
|
|
|
* @created 06.07.11 16:25 |
|
45
|
|
|
* |
|
46
|
|
|
* @method void denied() |
|
47
|
|
|
* @method void redirect(string $url) |
|
48
|
|
|
* @method void redirectTo(string $module, string $controller, array $params = array()) |
|
49
|
|
|
* @method void reload() |
|
50
|
|
|
* @method AbstractRowEntity user() |
|
51
|
|
|
*/ |
|
52
|
|
|
class Application |
|
53
|
|
|
{ |
|
54
|
|
|
use Common\Helper; |
|
55
|
|
|
use Common\Singleton; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @var string Application path |
|
59
|
|
|
*/ |
|
60
|
|
|
protected $path; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* @var string Environment name |
|
64
|
|
|
*/ |
|
65
|
|
|
protected $environment = 'production'; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* @var \Exception |
|
69
|
|
|
*/ |
|
70
|
|
|
protected $exception; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @var bool Debug application flag |
|
74
|
|
|
*/ |
|
75
|
|
|
protected $debugFlag = false; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* @var bool Layout flag |
|
79
|
|
|
*/ |
|
80
|
|
|
protected $layoutFlag = true; |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* @var array Stack of widgets closures |
|
84
|
|
|
*/ |
|
85
|
|
|
protected $widgets = array(); |
|
86
|
|
|
|
|
87
|
|
|
/** |
|
88
|
|
|
* @var array Stack of API closures |
|
89
|
|
|
*/ |
|
90
|
|
|
protected $api = array(); |
|
91
|
|
|
|
|
92
|
|
|
/** |
|
93
|
|
|
* Get application environment |
|
94
|
|
|
* |
|
95
|
|
|
* @return string |
|
96
|
|
|
*/ |
|
97
|
|
|
public function getEnvironment() |
|
98
|
|
|
{ |
|
99
|
|
|
return $this->environment; |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* Get path to Application |
|
104
|
|
|
* |
|
105
|
|
|
* @return string |
|
106
|
|
|
*/ |
|
107
|
669 |
|
public function getPath() |
|
108
|
|
|
{ |
|
109
|
669 |
|
if (!$this->path) { |
|
110
|
|
|
if (defined('PATH_APPLICATION')) { |
|
111
|
|
|
$this->path = PATH_APPLICATION; |
|
112
|
|
|
} else { |
|
113
|
|
|
$reflection = new \ReflectionClass($this); |
|
114
|
|
|
// 3 level up |
|
115
|
|
|
$this->path = dirname(dirname(dirname($reflection->getFileName()))); |
|
116
|
|
|
} |
|
117
|
|
|
} |
|
118
|
669 |
|
return $this->path; |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* Check debug flag |
|
123
|
|
|
* |
|
124
|
|
|
* @return bool |
|
125
|
|
|
*/ |
|
126
|
4 |
|
public function isDebug() |
|
127
|
|
|
{ |
|
128
|
4 |
|
return $this->debugFlag; |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* Check Layout flag |
|
133
|
|
|
* |
|
134
|
|
|
* @return bool |
|
135
|
|
|
*/ |
|
136
|
5 |
|
public function hasLayout() |
|
137
|
|
|
{ |
|
138
|
5 |
|
return $this->layoutFlag; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* Set Layout template and/or flag |
|
143
|
|
|
* |
|
144
|
|
|
* @param bool|string $flag |
|
145
|
|
|
* @return void |
|
146
|
|
|
*/ |
|
147
|
669 |
|
public function useLayout($flag = true) |
|
148
|
|
|
{ |
|
149
|
669 |
|
if (is_string($flag)) { |
|
150
|
|
|
Layout::setTemplate($flag); |
|
151
|
|
|
$this->layoutFlag = true; |
|
152
|
|
|
} else { |
|
153
|
669 |
|
$this->layoutFlag = $flag; |
|
154
|
|
|
} |
|
155
|
669 |
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Set JSON presentation |
|
159
|
|
|
* |
|
160
|
|
|
* @return void |
|
161
|
|
|
*/ |
|
162
|
|
|
public function useJson() |
|
163
|
|
|
{ |
|
164
|
|
|
// disable view and layout for JSON output |
|
165
|
|
|
$this->useLayout(false); |
|
166
|
|
|
Response::setHeader('Content-Type', 'application/json'); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* Initialize process |
|
171
|
|
|
* |
|
172
|
|
|
* @param string $environment |
|
173
|
|
|
* @throws ApplicationException |
|
174
|
|
|
* @return void |
|
175
|
|
|
*/ |
|
176
|
|
|
public function init($environment = 'production') |
|
177
|
|
|
{ |
|
178
|
|
|
$this->environment = $environment; |
|
179
|
|
|
|
|
180
|
|
|
try { |
|
181
|
|
|
// initial default helper path |
|
182
|
|
|
$this->addHelperPath(dirname(__FILE__) . '/Helper/'); |
|
183
|
|
|
|
|
184
|
|
|
// first log message |
|
185
|
|
|
Logger::info('app:init'); |
|
186
|
|
|
|
|
187
|
|
|
// setup configuration for current environment |
|
188
|
|
|
if ($debug = Config::getData('debug')) { |
|
189
|
|
|
$this->debugFlag = (bool) $debug; |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
// initial php settings |
|
193
|
|
|
if ($ini = Config::getData('php')) { |
|
194
|
|
|
foreach ($ini as $key => $value) { |
|
195
|
|
|
$result = ini_set($key, $value); |
|
196
|
|
|
Logger::info('app:init:php:'.$key.':'.($result?:'---')); |
|
197
|
|
|
} |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
// init session, start inside class |
|
201
|
|
|
Session::getInstance(); |
|
202
|
|
|
|
|
203
|
|
|
// init Messages |
|
204
|
|
|
Messages::getInstance(); |
|
205
|
|
|
|
|
206
|
|
|
// init Translator |
|
207
|
|
|
Translator::getInstance(); |
|
208
|
|
|
|
|
209
|
|
|
// init request |
|
210
|
|
|
$this->initRequest(); |
|
211
|
|
|
|
|
212
|
|
|
// init response |
|
213
|
|
|
$this->initResponse(); |
|
214
|
|
|
|
|
215
|
|
|
// init router |
|
216
|
|
|
$this->initRouter(); |
|
217
|
|
|
|
|
218
|
|
|
} catch (\Exception $e) { |
|
219
|
|
|
throw new ApplicationException("Application can't be loaded: " . $e->getMessage()); |
|
220
|
|
|
} |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
/** |
|
224
|
|
|
* Run application |
|
225
|
|
|
* |
|
226
|
|
|
* @return void |
|
227
|
|
|
*/ |
|
228
|
|
|
public function run() |
|
229
|
|
|
{ |
|
230
|
|
|
$this->process(); |
|
231
|
|
|
$this->render(); |
|
232
|
|
|
$this->finish(); |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
/** |
|
236
|
|
|
* Initial Request instance |
|
237
|
|
|
* |
|
238
|
|
|
* @return void |
|
239
|
|
|
*/ |
|
240
|
|
|
protected function initRequest() |
|
241
|
|
|
{ |
|
242
|
|
|
$request = RequestFactory::fromGlobals(); |
|
243
|
|
|
|
|
244
|
|
|
Request::setInstance($request); |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
/** |
|
248
|
|
|
* Initial Response instance |
|
249
|
|
|
* |
|
250
|
|
|
* @return void |
|
251
|
|
|
*/ |
|
252
|
|
|
protected function initResponse() |
|
253
|
|
|
{ |
|
254
|
|
|
$response = new ResponseInstance(); |
|
255
|
|
|
|
|
256
|
|
|
Response::setInstance($response); |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
/** |
|
260
|
|
|
* Initial Router instance |
|
261
|
|
|
* |
|
262
|
|
|
* @return void |
|
263
|
|
|
*/ |
|
264
|
669 |
|
protected function initRouter() |
|
265
|
|
|
{ |
|
266
|
669 |
|
$router = new \Bluz\Router\Router(); |
|
267
|
669 |
|
$router->setOptions(Config::getData('router')); |
|
268
|
|
|
|
|
269
|
669 |
|
Router::setInstance($router); |
|
270
|
669 |
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Initial controller view |
|
274
|
|
|
* |
|
275
|
|
|
* @param string $module |
|
276
|
|
|
* @param string $controller |
|
277
|
|
|
* @return View |
|
278
|
|
|
*/ |
|
279
|
2 |
|
protected function initView($module, $controller) |
|
280
|
|
|
{ |
|
281
|
|
|
// $view for use in closure |
|
282
|
2 |
|
$view = new View(); |
|
283
|
|
|
|
|
284
|
|
|
// setup additional helper path |
|
285
|
2 |
|
$view->addHelperPath($this->getPath() . '/layouts/helpers'); |
|
286
|
|
|
|
|
287
|
|
|
// setup additional partial path |
|
288
|
2 |
|
$view->addPartialPath($this->getPath() . '/layouts/partial'); |
|
289
|
|
|
|
|
290
|
|
|
// setup default path |
|
291
|
2 |
|
$view->setPath($this->getPath() . '/modules/' . $module . '/views'); |
|
292
|
|
|
|
|
293
|
|
|
// setup default template |
|
294
|
2 |
|
$view->setTemplate($controller . '.phtml'); |
|
295
|
|
|
|
|
296
|
2 |
|
return $view; |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
|
|
/** |
|
300
|
|
|
* Process application |
|
301
|
|
|
* |
|
302
|
|
|
* Note: |
|
303
|
|
|
* - Why you don't use "X-" prefix for custom headers? |
|
304
|
|
|
* - Because it deprecated ({@link http://tools.ietf.org/html/rfc6648}) |
|
305
|
|
|
* |
|
306
|
|
|
* @return void |
|
307
|
|
|
*/ |
|
308
|
2 |
|
public function process() |
|
309
|
|
|
{ |
|
310
|
2 |
|
Logger::info('app:process'); |
|
311
|
|
|
|
|
312
|
2 |
|
$this->preProcess(); |
|
313
|
2 |
|
$this->doProcess(); |
|
314
|
2 |
|
$this->postProcess(); |
|
315
|
2 |
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Pre process |
|
319
|
|
|
* |
|
320
|
|
|
* @return void |
|
321
|
|
|
* @throws ApplicationException |
|
322
|
|
|
*/ |
|
323
|
2 |
|
protected function preProcess() |
|
324
|
|
|
{ |
|
325
|
2 |
|
Logger::info("app:process:pre"); |
|
326
|
|
|
|
|
327
|
2 |
|
Router::process(); |
|
328
|
|
|
|
|
329
|
|
|
// disable layout for AJAX requests |
|
330
|
2 |
|
if (Request::isXmlHttpRequest()) { |
|
331
|
|
|
$this->useLayout(false); |
|
332
|
|
|
} |
|
333
|
2 |
|
} |
|
334
|
|
|
|
|
335
|
|
|
/** |
|
336
|
|
|
* Do process |
|
337
|
|
|
* |
|
338
|
|
|
* @return void |
|
339
|
|
|
*/ |
|
340
|
2 |
|
protected function doProcess() |
|
341
|
|
|
{ |
|
342
|
2 |
|
Logger::info("app:process:do"); |
|
343
|
|
|
|
|
344
|
2 |
|
$module = Request::getModule(); |
|
345
|
2 |
|
$controller = Request::getController(); |
|
346
|
2 |
|
$params = Request::getParams(); |
|
347
|
|
|
|
|
348
|
|
|
// try to dispatch controller |
|
349
|
|
|
try { |
|
350
|
|
|
// get reflection of requested controller |
|
351
|
2 |
|
$controllerFile = $this->getControllerFile($module, $controller); |
|
352
|
1 |
|
$reflection = $this->reflection($controllerFile); |
|
353
|
|
|
|
|
354
|
|
|
// check header "accept" for catch JSON requests, and switch response to JSON |
|
355
|
|
|
// it's some magic for AJAX and REST requests |
|
356
|
|
|
$acceptMap = [ |
|
357
|
1 |
|
'HTML' => 'text/html', |
|
358
|
|
|
'JSON' => 'application/json' |
|
359
|
|
|
]; |
|
360
|
|
|
|
|
361
|
|
|
// some controller has @accept tag |
|
362
|
1 |
|
if ($allowAccept = $reflection->getAccept()) { |
|
363
|
|
|
// convert list of controller @accept to MIME types |
|
364
|
|
|
$allowAccept = array_filter( |
|
365
|
|
|
$acceptMap, |
|
366
|
|
|
function ($key) use ($allowAccept) { |
|
367
|
|
|
return in_array($key, $allowAccept); |
|
368
|
|
|
}, |
|
369
|
|
|
ARRAY_FILTER_USE_KEY |
|
370
|
|
|
); |
|
371
|
|
|
$allowAccept = array_values($allowAccept); |
|
372
|
|
|
} else { |
|
373
|
|
|
// by default allow just HTML output |
|
374
|
1 |
|
$allowAccept = ['text/html']; |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
// choose MIME type by browser accept header |
|
378
|
|
|
// filtered by controller @accept |
|
379
|
1 |
|
$accept = Request::getAccept($allowAccept); |
|
380
|
|
|
|
|
381
|
|
|
// switch statement for Accept header |
|
382
|
|
|
switch ($accept) { |
|
383
|
1 |
|
case 'text/html': |
|
384
|
|
|
// HTML response with layout |
|
385
|
1 |
|
break; |
|
386
|
|
|
case 'application/json': |
|
387
|
|
|
$this->useJson(); |
|
388
|
|
|
break; |
|
389
|
|
|
default: |
|
390
|
|
|
if (PHP_SAPI == 'cli') { |
|
391
|
|
|
// all ok |
|
392
|
|
|
} else { |
|
393
|
|
|
// not acceptable MIME type |
|
394
|
|
|
throw new NotAcceptableException(); |
|
395
|
|
|
} |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
// check call method(s) |
|
399
|
1 |
|
if ($reflection->getMethod() && !in_array(Request::getMethod(), $reflection->getMethod())) { |
|
400
|
|
|
throw new NotAllowedException(join(',', $reflection->getMethod())); |
|
401
|
|
|
} |
|
402
|
|
|
|
|
403
|
|
|
// check HTML cache |
|
404
|
1 |
|
if ($reflection->getCacheHtml() && Request::getMethod() == Request::METHOD_GET) { |
|
405
|
|
|
$htmlKey = 'html:' . $module . ':' . $controller . ':' . http_build_query($params); |
|
406
|
|
|
if ($cachedHtml = Cache::get($htmlKey)) { |
|
407
|
|
|
Response::setBody($cachedHtml); |
|
408
|
|
|
return; |
|
409
|
|
|
} |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
// dispatch controller |
|
413
|
1 |
|
$dispatchResult = $this->dispatch($module, $controller, $params); |
|
414
|
|
|
|
|
415
|
1 |
|
} catch (RedirectException $e) { |
|
416
|
|
|
$this->setException($e); |
|
417
|
|
|
|
|
418
|
|
|
Response::removeHeaders(); |
|
419
|
|
|
Response::clearBody(); |
|
420
|
|
|
|
|
421
|
|
|
if (Request::isXmlHttpRequest()) { |
|
422
|
|
|
Response::setStatusCode(204); |
|
423
|
|
|
Response::setHeader('Bluz-Redirect', $e->getMessage()); |
|
424
|
|
|
return; |
|
425
|
|
|
} else { |
|
426
|
|
|
Response::setStatusCode(302); |
|
427
|
|
|
Response::setHeader('Location', $e->getMessage()); |
|
428
|
|
|
return; |
|
429
|
|
|
} |
|
430
|
1 |
|
} catch (ReloadException $e) { |
|
431
|
|
|
$this->setException($e); |
|
432
|
|
|
|
|
433
|
|
|
Response::removeHeaders(); |
|
434
|
|
|
Response::clearBody(); |
|
435
|
|
|
|
|
436
|
|
|
if (Request::isXmlHttpRequest()) { |
|
437
|
|
|
Response::setStatusCode(204); |
|
438
|
|
|
Response::setHeader('Bluz-Reload', 'true'); |
|
439
|
|
|
return; |
|
440
|
|
|
} else { |
|
441
|
|
|
Response::setStatusCode(302); |
|
442
|
|
|
Response::setHeader('Location', Request::getRequestUri()); |
|
443
|
|
|
return; |
|
444
|
|
|
} |
|
445
|
1 |
|
} catch (\Exception $e) { |
|
446
|
1 |
|
$this->setException($e); |
|
447
|
|
|
|
|
448
|
1 |
|
Response::removeHeaders(); |
|
449
|
1 |
|
Response::clearBody(); |
|
450
|
|
|
|
|
451
|
|
|
// cast to valid HTTP error code |
|
452
|
|
|
// 500 - Internal Server Error |
|
453
|
1 |
|
$statusCode = (100 <= $e->getCode() && $e->getCode() <= 505) ? $e->getCode() : 500; |
|
454
|
1 |
|
Response::setStatusCode($statusCode); |
|
455
|
|
|
|
|
456
|
|
|
// TODO: if `error` controller consist error |
|
457
|
1 |
|
$dispatchResult = $this->dispatch( |
|
458
|
1 |
|
Router::getErrorModule(), |
|
459
|
1 |
|
Router::getErrorController(), |
|
460
|
|
|
array( |
|
461
|
1 |
|
'code' => $e->getCode(), |
|
462
|
1 |
|
'message' => $e->getMessage() |
|
463
|
|
|
) |
|
464
|
|
|
); |
|
465
|
|
|
} |
|
466
|
|
|
|
|
467
|
2 |
|
if ($this->hasLayout()) { |
|
468
|
2 |
|
Layout::setContent($dispatchResult); |
|
469
|
2 |
|
$dispatchResult = Layout::getInstance(); |
|
470
|
|
|
} |
|
471
|
|
|
|
|
472
|
2 |
|
if (isset($htmlKey, $reflection)) { |
|
473
|
|
|
// @TODO: Added ETag header |
|
474
|
|
|
Cache::set($htmlKey, $dispatchResult(), $reflection->getCacheHtml()); |
|
475
|
|
|
Cache::addTag($htmlKey, $module); |
|
476
|
|
|
Cache::addTag($htmlKey, 'html'); |
|
477
|
|
|
Cache::addTag($htmlKey, 'html:' . $module); |
|
478
|
|
|
Cache::addTag($htmlKey, 'html:' . $module . ':' . $controller); |
|
479
|
|
|
} |
|
480
|
2 |
|
Response::setBody($dispatchResult); |
|
481
|
2 |
|
} |
|
482
|
|
|
|
|
483
|
|
|
/** |
|
484
|
|
|
* Post process |
|
485
|
|
|
* |
|
486
|
|
|
* @return void |
|
487
|
|
|
*/ |
|
488
|
2 |
|
protected function postProcess() |
|
489
|
|
|
{ |
|
490
|
2 |
|
Logger::info("app:process:post"); |
|
491
|
2 |
|
} |
|
492
|
|
|
|
|
493
|
|
|
/** |
|
494
|
|
|
* Dispatch controller with params |
|
495
|
|
|
* |
|
496
|
|
|
* Call dispatch from any \Bluz\Package |
|
497
|
|
|
* Application::getInstance()->dispatch($module, $controller, array $params); |
|
498
|
|
|
* |
|
499
|
|
|
* @param string $module |
|
500
|
|
|
* @param string $controller |
|
501
|
|
|
* @param array $params |
|
502
|
|
|
* @return View|callable |
|
503
|
|
|
* @throws ApplicationException |
|
504
|
|
|
*/ |
|
505
|
3 |
|
public function dispatch($module, $controller, $params = array()) |
|
506
|
|
|
{ |
|
507
|
3 |
|
Logger::info("app:dispatch: " . $module . '/' . $controller); |
|
508
|
|
|
|
|
509
|
3 |
|
$this->preDispatch($module, $controller, $params); |
|
510
|
2 |
|
$result = $this->doDispatch($module, $controller, $params); |
|
511
|
2 |
|
$this->postDispatch($module, $controller, $params); |
|
512
|
|
|
|
|
513
|
2 |
|
return $result; |
|
514
|
|
|
} |
|
515
|
|
|
|
|
516
|
|
|
/** |
|
517
|
|
|
* Pre dispatch mount point |
|
518
|
|
|
* |
|
519
|
|
|
* @param string $module |
|
520
|
|
|
* @param string $controller |
|
521
|
|
|
* @param array $params |
|
522
|
|
|
* @return void |
|
523
|
|
|
*/ |
|
524
|
3 |
|
protected function preDispatch($module, $controller, $params = array()) |
|
525
|
|
|
{ |
|
526
|
3 |
|
Logger::info("---:dispatch:pre: " . $module . '/' . $controller); |
|
527
|
|
|
|
|
528
|
|
|
// check privilege before run controller |
|
529
|
3 |
|
if (!$this->isAllowed($module, $controller)) { |
|
530
|
|
|
$this->denied(); |
|
531
|
|
|
} |
|
532
|
2 |
|
} |
|
533
|
|
|
|
|
534
|
|
|
/** |
|
535
|
|
|
* Do dispatch |
|
536
|
|
|
* |
|
537
|
|
|
* @param string $module |
|
538
|
|
|
* @param string $controller |
|
539
|
|
|
* @param array $params |
|
540
|
|
|
* @return View|callable |
|
541
|
|
|
* @throws ApplicationException |
|
542
|
|
|
*/ |
|
543
|
2 |
|
protected function doDispatch($module, $controller, $params = array()) |
|
544
|
|
|
{ |
|
545
|
2 |
|
Logger::info("---:dispatch:do: " . $module . '/' . $controller); |
|
546
|
2 |
|
$controllerFile = $this->getControllerFile($module, $controller); |
|
547
|
2 |
|
$reflection = $this->reflection($controllerFile); |
|
548
|
|
|
|
|
549
|
2 |
|
if ($reflection->getCache()) { |
|
550
|
|
|
$cacheKey = 'view:' . $module . ':' . $controller . ':' . http_build_query($params); |
|
551
|
|
|
if ($cachedView = Cache::get($cacheKey)) { |
|
552
|
|
|
return $cachedView; |
|
553
|
|
|
} |
|
554
|
|
|
} |
|
555
|
|
|
|
|
556
|
|
|
// process params |
|
557
|
2 |
|
$params = $reflection->params($params); |
|
558
|
|
|
|
|
559
|
2 |
|
$view = $this->initView($module, $controller); |
|
560
|
|
|
|
|
561
|
2 |
|
$bootstrapPath = $this->getPath() . '/modules/' . $module . '/bootstrap.php'; |
|
562
|
|
|
|
|
563
|
|
|
/** |
|
564
|
|
|
* optional $bootstrap for use in closure |
|
565
|
|
|
* @var \closure $bootstrap |
|
566
|
|
|
*/ |
|
567
|
2 |
|
if (file_exists($bootstrapPath)) { |
|
568
|
|
|
$bootstrap = require $bootstrapPath; |
|
569
|
|
|
} else { |
|
570
|
2 |
|
$bootstrap = null; |
|
571
|
|
|
} |
|
572
|
2 |
|
unset($bootstrapPath); |
|
573
|
|
|
|
|
574
|
|
|
/** |
|
575
|
|
|
* @var \closure $controllerClosure |
|
576
|
|
|
*/ |
|
577
|
2 |
|
$controllerClosure = include $controllerFile; |
|
578
|
|
|
|
|
579
|
2 |
|
if (!is_callable($controllerClosure)) { |
|
580
|
|
|
throw new ApplicationException("Controller is not callable '$module/$controller'"); |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
2 |
|
$result = $controllerClosure(...$params); |
|
584
|
|
|
|
|
585
|
|
|
// switch statement for result of Closure run |
|
586
|
|
|
switch (true) { |
|
587
|
2 |
|
case ($result === false): |
|
588
|
|
|
// return "false" is equal to disable view and layout |
|
589
|
|
|
$this->useLayout(false); |
|
590
|
|
|
return null; |
|
591
|
2 |
|
case is_callable($result): |
|
592
|
1 |
|
case is_object($result): |
|
593
|
|
|
// return callable structure (closure or object) for replace logic of controller |
|
594
|
|
|
// or return any class |
|
|
|
|
|
|
595
|
1 |
|
return $result; |
|
596
|
1 |
|
case is_string($result): |
|
597
|
|
|
// return string variable is equal to change view template |
|
598
|
|
|
$view->setTemplate($result); |
|
599
|
|
|
break; |
|
600
|
1 |
|
case is_array($result): |
|
601
|
|
|
// return associative array is equal to setup view data |
|
602
|
|
|
$view->setFromArray($result); |
|
603
|
|
|
break; |
|
604
|
|
|
} |
|
605
|
|
|
|
|
606
|
1 |
|
if (isset($cacheKey)) { |
|
607
|
|
|
Cache::set($cacheKey, $view, $reflection->getCache()); |
|
608
|
|
|
Cache::addTag($cacheKey, $module); |
|
609
|
|
|
Cache::addTag($cacheKey, 'view'); |
|
610
|
|
|
Cache::addTag($cacheKey, 'view:' . $module); |
|
611
|
|
|
Cache::addTag($cacheKey, 'view:' . $module . ':' . $controller); |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
1 |
|
return $view; |
|
615
|
|
|
} |
|
616
|
|
|
|
|
617
|
|
|
/** |
|
618
|
|
|
* Post dispatch mount point |
|
619
|
|
|
* |
|
620
|
|
|
* @param string $module |
|
621
|
|
|
* @param string $controller |
|
622
|
|
|
* @param array $params |
|
623
|
|
|
* @return void |
|
624
|
|
|
*/ |
|
625
|
2 |
|
protected function postDispatch($module, $controller, $params = array()) |
|
626
|
|
|
{ |
|
627
|
2 |
|
Logger::info("---:dispatch:post: " . $module . '/' . $controller); |
|
628
|
2 |
|
} |
|
629
|
|
|
|
|
630
|
|
|
/** |
|
631
|
|
|
* Render, is send Response |
|
632
|
|
|
* |
|
633
|
|
|
* @return void |
|
634
|
|
|
*/ |
|
635
|
|
|
public function render() |
|
636
|
|
|
{ |
|
637
|
|
|
Logger::info('app:render'); |
|
638
|
|
|
|
|
639
|
|
|
Response::send(); |
|
640
|
|
|
} |
|
641
|
|
|
|
|
642
|
|
|
/** |
|
643
|
|
|
* Get Response instance |
|
644
|
|
|
* |
|
645
|
|
|
* @return \Bluz\Response\Response |
|
646
|
|
|
*/ |
|
647
|
1 |
|
public function getResponse() |
|
648
|
|
|
{ |
|
649
|
1 |
|
return Response::getInstance(); |
|
650
|
|
|
} |
|
651
|
|
|
|
|
652
|
|
|
/** |
|
653
|
|
|
* Get Request instance |
|
654
|
|
|
* |
|
655
|
|
|
* @return \Zend\Diactoros\ServerRequest |
|
656
|
|
|
*/ |
|
657
|
1 |
|
public function getRequest() |
|
658
|
|
|
{ |
|
659
|
1 |
|
return Request::getInstance(); |
|
660
|
|
|
} |
|
661
|
|
|
|
|
662
|
|
|
/** |
|
663
|
|
|
* setException |
|
664
|
|
|
* |
|
665
|
|
|
* @param \Exception $exception |
|
666
|
|
|
* @return void |
|
667
|
|
|
*/ |
|
668
|
1 |
|
public function setException($exception) |
|
669
|
|
|
{ |
|
670
|
1 |
|
$this->exception = $exception; |
|
671
|
1 |
|
} |
|
672
|
|
|
|
|
673
|
|
|
/** |
|
674
|
|
|
* getException |
|
675
|
|
|
* |
|
676
|
|
|
* @return \Exception |
|
677
|
|
|
*/ |
|
678
|
|
|
public function getException() |
|
679
|
|
|
{ |
|
680
|
|
|
return $this->exception; |
|
681
|
|
|
} |
|
682
|
|
|
|
|
683
|
|
|
/** |
|
684
|
|
|
* Widget call |
|
685
|
|
|
* |
|
686
|
|
|
* Call widget from any \Bluz\Package |
|
687
|
|
|
* <code> |
|
688
|
|
|
* Application::getInstance()->widget($module, $widget, array $params); |
|
689
|
|
|
* </code> |
|
690
|
|
|
* |
|
691
|
|
|
* @param string $module |
|
692
|
|
|
* @param string $widget |
|
693
|
|
|
* @param array $params |
|
694
|
|
|
* @return \Closure |
|
695
|
|
|
* @throws ApplicationException |
|
696
|
|
|
*/ |
|
697
|
1 |
|
public function widget($module, $widget, $params = array()) |
|
698
|
|
|
{ |
|
699
|
1 |
|
Logger::info("app:widget: " . $module . '/' . $widget); |
|
700
|
1 |
|
$widgetFile = $this->getWidgetFile($module, $widget); |
|
701
|
|
|
$reflection = $this->reflection($widgetFile); |
|
702
|
|
|
|
|
703
|
|
|
// check acl |
|
704
|
|
|
if (!Acl::isAllowed($module, $reflection->getPrivilege())) { |
|
705
|
|
|
throw new ForbiddenException("Not enough permissions for call widget '$module/$widget'"); |
|
706
|
|
|
} |
|
707
|
|
|
|
|
708
|
|
|
/** |
|
709
|
|
|
* Cachable widgets |
|
710
|
|
|
* @var \Closure $widgetClosure |
|
711
|
|
|
*/ |
|
712
|
|
|
if (isset($this->widgets[$module], $this->widgets[$module][$widget])) { |
|
713
|
|
|
$widgetClosure = $this->widgets[$module][$widget]; |
|
714
|
|
|
} else { |
|
715
|
|
|
$widgetClosure = include $widgetFile; |
|
716
|
|
|
|
|
717
|
|
|
if (!isset($this->widgets[$module])) { |
|
718
|
|
|
$this->widgets[$module] = array(); |
|
719
|
|
|
} |
|
720
|
|
|
$this->widgets[$module][$widget] = $widgetClosure; |
|
721
|
|
|
} |
|
722
|
|
|
|
|
723
|
|
|
if (!is_callable($widgetClosure)) { |
|
724
|
|
|
throw new ApplicationException("Widget is not callable '$module/$widget'"); |
|
725
|
|
|
} |
|
726
|
|
|
|
|
727
|
|
|
return $widgetClosure; |
|
728
|
|
|
} |
|
729
|
|
|
|
|
730
|
|
|
/** |
|
731
|
|
|
* Api call |
|
732
|
|
|
* |
|
733
|
|
|
* Call API from any \Bluz\Package |
|
734
|
|
|
* <code> |
|
735
|
|
|
* Application::getInstance()->api($module, $widget, array $params); |
|
736
|
|
|
* </code> |
|
737
|
|
|
* |
|
738
|
|
|
* @param string $module |
|
739
|
|
|
* @param string $method |
|
740
|
|
|
* @return \Closure |
|
741
|
|
|
* @throws ApplicationException |
|
742
|
|
|
*/ |
|
743
|
1 |
|
public function api($module, $method) |
|
744
|
|
|
{ |
|
745
|
1 |
|
Logger::info("app:api: " . $module . '/' . $method); |
|
746
|
|
|
|
|
747
|
|
|
/** |
|
748
|
|
|
* Cachable APIs |
|
749
|
|
|
* @var \Closure $widgetClosure |
|
750
|
|
|
*/ |
|
751
|
1 |
|
if (isset($this->api[$module]) |
|
752
|
1 |
|
&& isset($this->api[$module][$method]) |
|
753
|
|
|
) { |
|
754
|
|
|
$apiClosure = $this->api[$module][$method]; |
|
755
|
|
|
} else { |
|
756
|
1 |
|
$apiClosure = require $this->getApiFile($module, $method); |
|
757
|
|
|
|
|
758
|
|
|
if (!isset($this->api[$module])) { |
|
759
|
|
|
$this->api[$module] = array(); |
|
760
|
|
|
} |
|
761
|
|
|
$this->api[$module][$method] = $apiClosure; |
|
762
|
|
|
} |
|
763
|
|
|
|
|
764
|
|
|
if (!is_callable($apiClosure)) { |
|
765
|
|
|
throw new ApplicationException("API is not callable '$module/$method'"); |
|
766
|
|
|
} |
|
767
|
|
|
|
|
768
|
|
|
return $apiClosure; |
|
769
|
|
|
} |
|
770
|
|
|
|
|
771
|
|
|
/** |
|
772
|
|
|
* Retrieve reflection for anonymous function |
|
773
|
|
|
* |
|
774
|
|
|
* @param string $file |
|
775
|
|
|
* @return Reflection |
|
776
|
|
|
* @throws ApplicationException |
|
777
|
|
|
*/ |
|
778
|
670 |
|
public function reflection($file) |
|
779
|
|
|
{ |
|
780
|
|
|
// cache for reflection data |
|
781
|
670 |
|
if (!$reflection = Cache::get('reflection:' . $file)) { |
|
782
|
670 |
|
$reflection = new Reflection($file); |
|
783
|
670 |
|
$reflection->process(); |
|
784
|
|
|
|
|
785
|
670 |
|
Cache::set('reflection:' . $file, $reflection); |
|
786
|
670 |
|
Cache::addTag('reflection:' . $file, 'reflection'); |
|
787
|
|
|
} |
|
788
|
670 |
|
return $reflection; |
|
789
|
|
|
} |
|
790
|
|
|
|
|
791
|
|
|
/** |
|
792
|
|
|
* Is allowed controller/widget/etc |
|
793
|
|
|
* |
|
794
|
|
|
* @param string $module |
|
795
|
|
|
* @param string $controller |
|
796
|
|
|
* @return bool |
|
797
|
|
|
* @throws ApplicationException |
|
798
|
|
|
*/ |
|
799
|
4 |
|
public function isAllowed($module, $controller) |
|
800
|
|
|
{ |
|
801
|
4 |
|
$file = $this->getControllerFile($module, $controller); |
|
802
|
2 |
|
$reflection = $this->reflection($file); |
|
803
|
|
|
|
|
804
|
2 |
|
if ($privilege = $reflection->getPrivilege()) { |
|
805
|
|
|
return Acl::isAllowed($module, $privilege); |
|
806
|
|
|
} |
|
807
|
2 |
|
return true; |
|
808
|
|
|
} |
|
809
|
|
|
|
|
810
|
|
|
/** |
|
811
|
|
|
* Get controller file |
|
812
|
|
|
* |
|
813
|
|
|
* @param string $module |
|
814
|
|
|
* @param string $controller |
|
815
|
|
|
* @return string |
|
816
|
|
|
* @throws ApplicationException |
|
817
|
|
|
*/ |
|
818
|
4 |
|
protected function getControllerFile($module, $controller) |
|
819
|
|
|
{ |
|
820
|
4 |
|
$controllerPath = $this->getPath() . '/modules/' . $module |
|
821
|
4 |
|
. '/controllers/' . $controller . '.php'; |
|
822
|
|
|
|
|
823
|
4 |
|
if (!file_exists($controllerPath)) { |
|
824
|
3 |
|
throw new ApplicationException("Controller file not found '$module/$controller'", 404); |
|
825
|
|
|
} |
|
826
|
|
|
|
|
827
|
2 |
|
return $controllerPath; |
|
828
|
|
|
} |
|
829
|
|
|
|
|
830
|
|
|
/** |
|
831
|
|
|
* Get widget file |
|
832
|
|
|
* |
|
833
|
|
|
* @param string $module |
|
834
|
|
|
* @param string $widget |
|
835
|
|
|
* @return string |
|
836
|
|
|
* @throws ApplicationException |
|
837
|
|
|
*/ |
|
838
|
1 |
|
protected function getWidgetFile($module, $widget) |
|
839
|
|
|
{ |
|
840
|
1 |
|
$widgetPath = $this->getPath() . '/modules/' . $module |
|
841
|
1 |
|
. '/widgets/' . $widget . '.php'; |
|
842
|
|
|
|
|
843
|
1 |
|
if (!file_exists($widgetPath)) { |
|
844
|
1 |
|
throw new ApplicationException("Widget file not found '$module/$widget'"); |
|
845
|
|
|
} |
|
846
|
|
|
|
|
847
|
|
|
return $widgetPath; |
|
848
|
|
|
} |
|
849
|
|
|
|
|
850
|
|
|
/** |
|
851
|
|
|
* Get API file |
|
852
|
|
|
* |
|
853
|
|
|
* @param string $module |
|
854
|
|
|
* @param string $method |
|
855
|
|
|
* @return string |
|
856
|
|
|
* @throws ApplicationException |
|
857
|
|
|
*/ |
|
858
|
1 |
|
protected function getApiFile($module, $method) |
|
859
|
|
|
{ |
|
860
|
1 |
|
$apiPath = $this->getPath() . '/modules/' . $module |
|
861
|
1 |
|
. '/api/' . $method . '.php'; |
|
862
|
|
|
|
|
863
|
1 |
|
if (!file_exists($apiPath)) { |
|
864
|
1 |
|
throw new ApplicationException("API file not found '$module/$method'"); |
|
865
|
|
|
} |
|
866
|
|
|
|
|
867
|
|
|
return $apiPath; |
|
868
|
|
|
} |
|
869
|
|
|
|
|
870
|
|
|
/** |
|
871
|
|
|
* Finally method |
|
872
|
|
|
* |
|
873
|
|
|
* @return void |
|
874
|
|
|
*/ |
|
875
|
|
|
public function finish() |
|
876
|
|
|
{ |
|
877
|
|
|
Logger::info('app:finish'); |
|
878
|
|
|
} |
|
879
|
|
|
} |
|
880
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.