Completed
Pull Request — master (#393)
by Anton
04:48
created

Controller::checkPrivilege()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.576

Importance

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