Completed
Pull Request — master (#363)
by Anton
05:32
created

Controller::setFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 2
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
/**
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
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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