|
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\Controller; |
|
13
|
|
|
|
|
14
|
|
|
use Bluz\Application\Application; |
|
15
|
|
|
use Bluz\Application\Exception\ForbiddenException; |
|
16
|
|
|
use Bluz\Application\Exception\NotAcceptableException; |
|
17
|
|
|
use Bluz\Application\Exception\NotAllowedException; |
|
18
|
|
|
use Bluz\Auth\AbstractRowEntity; |
|
19
|
|
|
use Bluz\Common\Helper; |
|
20
|
|
|
use Bluz\Proxy\Acl; |
|
21
|
|
|
use Bluz\Proxy\Cache; |
|
22
|
|
|
use Bluz\Proxy\Request; |
|
23
|
|
|
use Bluz\Proxy\Response; |
|
24
|
|
|
use Bluz\View\View; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* Statement |
|
28
|
|
|
* |
|
29
|
|
|
* @package Bluz\Controller |
|
30
|
|
|
* @author Anton Shevchuk |
|
31
|
|
|
* |
|
32
|
|
|
* @method void denied() |
|
33
|
|
|
* @method void disableLayout() |
|
34
|
|
|
* @method void disableView() |
|
35
|
|
|
* @method Controller dispatch(string $module, string $controller, array $params = array()) |
|
36
|
|
|
* @method void redirect(string $url) |
|
37
|
|
|
* @method void redirectTo(string $module, string $controller, array $params = array()) |
|
38
|
|
|
* @method void reload() |
|
39
|
|
|
* @method void useJson() |
|
40
|
|
|
* @method void useLayout($layout) |
|
41
|
|
|
* @method AbstractRowEntity user() |
|
42
|
|
|
*/ |
|
43
|
|
|
class Controller implements \JsonSerializable |
|
44
|
|
|
{ |
|
45
|
|
|
use Helper; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* @var string |
|
49
|
|
|
*/ |
|
50
|
|
|
protected $module; |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* @var string |
|
54
|
|
|
*/ |
|
55
|
|
|
protected $controller; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @var string |
|
59
|
|
|
*/ |
|
60
|
|
|
protected $template; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* @var string |
|
64
|
|
|
*/ |
|
65
|
|
|
protected $file; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* @var Reflection |
|
69
|
|
|
*/ |
|
70
|
|
|
protected $reflection; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @var Data |
|
74
|
|
|
*/ |
|
75
|
|
|
protected $data; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* One of HTML, JSON or empty string |
|
79
|
|
|
* @var string |
|
80
|
|
|
*/ |
|
81
|
|
|
protected $render = 'HTML'; |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* Constructor of Statement |
|
85
|
|
|
* |
|
86
|
|
|
* @param string $module |
|
87
|
|
|
* @param string $controller |
|
88
|
|
|
*/ |
|
89
|
632 |
|
public function __construct($module, $controller) |
|
90
|
|
|
{ |
|
91
|
632 |
|
$this->module = $module; |
|
92
|
632 |
|
$this->controller = $controller; |
|
93
|
632 |
|
$this->template = $controller . '.phtml'; |
|
94
|
|
|
|
|
95
|
|
|
// initial default helper path |
|
96
|
632 |
|
$this->addHelperPath(dirname(__FILE__) . '/Helper/'); |
|
97
|
632 |
|
} |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* Check `Privilege` |
|
101
|
|
|
* |
|
102
|
|
|
* @throws ForbiddenException |
|
103
|
|
|
*/ |
|
104
|
5 |
|
public function checkPrivilege() |
|
105
|
|
|
{ |
|
106
|
5 |
|
if ($privilege = $this->getReflection()->getPrivilege()) { |
|
107
|
|
|
if (!Acl::isAllowed($this->module, $privilege)) { |
|
108
|
|
|
throw new ForbiddenException; |
|
109
|
|
|
} |
|
110
|
|
|
} |
|
111
|
4 |
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* Check `Method` |
|
115
|
|
|
* |
|
116
|
|
|
* @throws NotAllowedException |
|
117
|
|
|
*/ |
|
118
|
5 |
|
public function checkMethod() |
|
119
|
|
|
{ |
|
120
|
5 |
|
if ($this->getReflection()->getMethod() |
|
121
|
4 |
|
&& !in_array(Request::getMethod(), $this->getReflection()->getMethod())) { |
|
122
|
|
|
Response::setHeader('Allow', join(',', $this->getReflection()->getMethod())); |
|
123
|
|
|
throw new NotAllowedException; |
|
124
|
|
|
} |
|
125
|
4 |
|
} |
|
126
|
|
|
|
|
127
|
|
|
/** |
|
128
|
|
|
* Check `Accept` |
|
129
|
|
|
* |
|
130
|
|
|
* @throws NotAcceptableException |
|
131
|
|
|
*/ |
|
132
|
5 |
|
public function checkAccept() |
|
133
|
|
|
{ |
|
134
|
|
|
// all ok for CLI |
|
135
|
5 |
|
if (PHP_SAPI == 'cli') { |
|
136
|
5 |
|
return; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
$allowAccept = $this->getReflection()->getAccept(); |
|
140
|
|
|
|
|
141
|
|
|
// some controllers hasn't @accept tag |
|
142
|
|
|
if (!$allowAccept) { |
|
143
|
|
|
// but by default allow just HTML output |
|
144
|
|
|
$allowAccept = [Request::TYPE_HTML, Request::TYPE_ANY]; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
// get Accept with high priority |
|
148
|
|
|
$accept = Request::getAccept($allowAccept); |
|
149
|
|
|
|
|
150
|
|
|
// some controllers allow any type (*/*) |
|
151
|
|
|
// and client doesn't send Accept header |
|
152
|
|
|
if (in_array(Request::TYPE_ANY, $allowAccept) && !$accept) { |
|
153
|
|
|
// all OK, controller should realize logic for response |
|
154
|
|
|
return; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
// some controllers allow just selected types |
|
158
|
|
|
// choose MIME type by browser accept header |
|
159
|
|
|
// filtered by controller @accept |
|
160
|
|
|
// switch statement for this logic |
|
161
|
|
|
switch ($accept) { |
|
162
|
|
|
case Request::TYPE_ANY: |
|
163
|
|
|
case Request::TYPE_HTML: |
|
164
|
|
|
// HTML response with layout |
|
165
|
|
|
break; |
|
166
|
|
|
case Request::TYPE_JSON: |
|
167
|
|
|
// JSON response |
|
168
|
|
|
$this->template = null; |
|
169
|
|
|
break; |
|
170
|
|
|
default: |
|
171
|
|
|
throw new NotAcceptableException; |
|
172
|
|
|
} |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* __invoke |
|
177
|
|
|
* |
|
178
|
|
|
* @param array $params |
|
179
|
|
|
* @return Data |
|
180
|
|
|
* @throws ControllerException |
|
181
|
|
|
*/ |
|
182
|
4 |
|
public function run($params = []) // : array |
|
183
|
|
|
{ |
|
184
|
|
|
// initial variables for use inside controller |
|
185
|
4 |
|
$module = $this->module; |
|
186
|
4 |
|
$controller = $this->controller; |
|
187
|
|
|
|
|
188
|
4 |
|
$cacheKey = 'data:' . $module . ':' . $controller . ':' . http_build_query($params); |
|
189
|
|
|
|
|
190
|
4 |
|
if ($this->getReflection()->getCache()) { |
|
191
|
|
|
if ($cached = Cache::get($cacheKey)) { |
|
192
|
|
|
return $cached; |
|
193
|
|
|
} |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
4 |
|
$data = $this->getData(); |
|
197
|
|
|
|
|
198
|
|
|
/** |
|
199
|
|
|
* @var \closure $controllerClosure |
|
200
|
|
|
*/ |
|
201
|
4 |
|
$controllerClosure = include $this->getFile(); |
|
202
|
|
|
|
|
203
|
4 |
|
if (!is_callable($controllerClosure)) { |
|
204
|
|
|
throw new ControllerException("Controller is not callable '{$module}/{$controller}'"); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
// process params |
|
208
|
4 |
|
$params = $this->getReflection()->params($params); |
|
209
|
|
|
|
|
210
|
|
|
// call Closure or Controller |
|
211
|
4 |
|
$result = $controllerClosure(...$params); |
|
212
|
|
|
|
|
213
|
|
|
// switch statement for result of Closure run |
|
214
|
|
|
switch (true) { |
|
215
|
4 |
|
case ($result === false): |
|
216
|
|
|
// return "false" is equal to disable view and layout |
|
217
|
|
|
$this->disableLayout(); |
|
218
|
|
|
$this->disableView(); |
|
219
|
|
|
break; |
|
220
|
4 |
|
case is_string($result): |
|
221
|
|
|
// return string variable is equal to change view template |
|
222
|
|
|
$this->template = $result; |
|
223
|
|
|
break; |
|
224
|
4 |
|
case is_array($result): |
|
225
|
|
|
// return associative array is equal to setup view data |
|
226
|
1 |
|
$this->getData()->setFromArray($result); |
|
227
|
1 |
|
break; |
|
228
|
3 |
|
case ($result instanceof Controller): |
|
229
|
|
|
$this->getData()->setFromArray($result->getData()->toArray()); |
|
230
|
|
|
break; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
4 |
|
if ($this->getReflection()->getCache()) { |
|
234
|
|
|
Cache::set($cacheKey, $this->getData(), $this->getReflection()->getCache()); |
|
235
|
|
|
Cache::addTag($cacheKey, $module); |
|
236
|
|
|
Cache::addTag($cacheKey, 'data'); |
|
237
|
|
|
Cache::addTag($cacheKey, 'data:' . $module . ':' . $controller); |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
4 |
|
return $this->getData(); |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
/** |
|
244
|
|
|
* Setup controller file |
|
245
|
|
|
* |
|
246
|
|
|
* @return void |
|
247
|
|
|
* @throws ControllerException |
|
248
|
|
|
*/ |
|
249
|
632 |
|
protected function setFile() |
|
250
|
|
|
{ |
|
251
|
632 |
|
$path = Application::getInstance()->getPath(); |
|
252
|
632 |
|
$file = $path . '/modules/' . $this->module . '/controllers/' . $this->controller . '.php'; |
|
253
|
|
|
|
|
254
|
632 |
|
if (!file_exists($file)) { |
|
255
|
3 |
|
throw new ControllerException("Controller file not found '{$this->module}/{$this->controller}'", 404); |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
632 |
|
$this->file = $file; |
|
259
|
632 |
|
} |
|
260
|
|
|
|
|
261
|
|
|
/** |
|
262
|
|
|
* Get controller file path |
|
263
|
|
|
* @return string |
|
264
|
|
|
*/ |
|
265
|
632 |
|
protected function getFile() // : string |
|
266
|
|
|
{ |
|
267
|
632 |
|
if (!$this->file) { |
|
268
|
632 |
|
$this->setFile(); |
|
269
|
|
|
} |
|
270
|
632 |
|
return $this->file; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
/** |
|
274
|
|
|
* Retrieve reflection for anonymous function |
|
275
|
|
|
* @return Reflection |
|
276
|
|
|
* @throws \Bluz\Common\Exception\ComponentException |
|
277
|
|
|
*/ |
|
278
|
632 |
|
protected function setReflection() |
|
279
|
|
|
{ |
|
280
|
|
|
// cache for reflection data |
|
281
|
632 |
|
if (!$reflection = Cache::get('reflection:' . $this->module . ':' . $this->controller)) { |
|
282
|
632 |
|
$reflection = new Reflection($this->getFile()); |
|
283
|
632 |
|
$reflection->process(); |
|
284
|
|
|
|
|
285
|
632 |
|
Cache::set('reflection:' . $this->module . ':' . $this->controller, $reflection); |
|
286
|
632 |
|
Cache::addTag('reflection:' . $this->module . ':' . $this->controller, 'reflection'); |
|
287
|
|
|
} |
|
288
|
632 |
|
$this->reflection = $reflection; |
|
289
|
632 |
|
} |
|
290
|
|
|
|
|
291
|
|
|
/** |
|
292
|
|
|
* Get Reflection |
|
293
|
|
|
* @return Reflection |
|
294
|
|
|
*/ |
|
295
|
632 |
|
public function getReflection() // : Reflection |
|
296
|
|
|
{ |
|
297
|
632 |
|
if (!$this->reflection) { |
|
298
|
632 |
|
$this->setReflection(); |
|
299
|
|
|
} |
|
300
|
632 |
|
return $this->reflection; |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
/** |
|
304
|
|
|
* Assign key/value pair to Data object |
|
305
|
|
|
* @param string $key |
|
306
|
|
|
* @param mixed $value |
|
307
|
|
|
* @return Controller |
|
308
|
|
|
*/ |
|
309
|
|
|
public function assign($key, $value) |
|
310
|
|
|
{ |
|
311
|
|
|
$this->getData()->set($key, $value); |
|
312
|
|
|
return $this; |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
/** |
|
316
|
|
|
* Get controller Data container |
|
317
|
|
|
* |
|
318
|
|
|
* @return Data |
|
319
|
|
|
*/ |
|
320
|
4 |
|
public function getData() |
|
321
|
|
|
{ |
|
322
|
4 |
|
if (!$this->data) { |
|
323
|
4 |
|
$this->data = new Data(); |
|
324
|
|
|
} |
|
325
|
4 |
|
return $this->data; |
|
326
|
|
|
} |
|
327
|
|
|
|
|
328
|
|
|
/** |
|
329
|
|
|
* Render controller |
|
330
|
|
|
* |
|
331
|
|
|
* @param string $type |
|
332
|
|
|
* @return mixed |
|
333
|
|
|
*/ |
|
334
|
2 |
|
public function render($type = 'HTML') |
|
335
|
|
|
{ |
|
336
|
|
|
// switch statement for $type |
|
|
|
|
|
|
337
|
2 |
|
switch (strtoupper($type)) { |
|
338
|
2 |
|
case 'CLI': |
|
339
|
2 |
|
case 'JSON': |
|
340
|
|
|
return $this->getData(); |
|
341
|
2 |
|
case 'HTML': |
|
342
|
2 |
|
if (!$this->template) { |
|
343
|
|
|
return ''; |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
// $view for use in closure |
|
347
|
2 |
|
$view = new View(); |
|
348
|
|
|
|
|
349
|
2 |
|
$path = Application::getInstance()->getPath(); |
|
350
|
|
|
|
|
351
|
|
|
// setup additional helper path |
|
352
|
2 |
|
$view->addHelperPath($path . '/layouts/helpers'); |
|
353
|
|
|
|
|
354
|
|
|
// setup additional partial path |
|
355
|
2 |
|
$view->addPartialPath($path . '/layouts/partial'); |
|
356
|
|
|
|
|
357
|
|
|
// setup default path |
|
358
|
2 |
|
$view->setPath($path . '/modules/' . $this->module . '/views'); |
|
359
|
|
|
|
|
360
|
|
|
// setup template |
|
361
|
2 |
|
$view->setTemplate($this->template); |
|
362
|
|
|
|
|
363
|
|
|
// setup data |
|
364
|
2 |
|
$view->setFromArray($this->getData()->toArray()); |
|
365
|
2 |
|
return $view->render(); |
|
366
|
|
|
default: |
|
367
|
|
|
return ''; |
|
368
|
|
|
} |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
/** |
|
372
|
|
|
* Specify data which should be serialized to JSON |
|
373
|
|
|
* |
|
374
|
|
|
* @return array |
|
375
|
|
|
*/ |
|
376
|
|
|
public function jsonSerialize() |
|
377
|
|
|
{ |
|
378
|
|
|
return $this->render('JSON'); |
|
379
|
|
|
} |
|
380
|
|
|
|
|
381
|
|
|
/** |
|
382
|
|
|
* Magic cast to string |
|
383
|
|
|
* |
|
384
|
|
|
* @return string |
|
385
|
|
|
*/ |
|
386
|
|
|
public function __toString() |
|
387
|
|
|
{ |
|
388
|
|
|
return $this->render('HTML'); |
|
389
|
|
|
} |
|
390
|
|
|
} |
|
391
|
|
|
|
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.