Completed
Push — master ( 070a1e...ccce77 )
by Anton
12s
created

Controller::checkAccept()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 43.7413

Importance

Changes 0
Metric Value
cc 8
eloc 18
nc 11
nop 0
dl 0
loc 42
ccs 3
cts 17
cp 0.1765
crap 43.7413
rs 5.3846
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\Logger;
22
use Bluz\Proxy\Request;
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 attachment(string $file)
33
 * @method void denied()
34
 * @method void disableLayout()
35
 * @method void disableView()
36
 * @method Controller dispatch(string $module, string $controller, array $params = [])
37
 * @method void redirect(string $url)
38
 * @method void redirectTo(string $module, string $controller, array $params = [])
39
 * @method void reload()
40
 * @method void useJson()
41
 * @method void useLayout($layout)
42
 * @method EntityInterface user()
43
 */
44
class Controller implements \JsonSerializable
45
{
46
    use Helper;
47
    use ResponseTrait;
48
49
    /**
50
     * @var string
51
     */
52
    protected $module;
53
54
    /**
55
     * @var string
56
     */
57
    protected $controller;
58
59
    /**
60
     * @var string
61
     */
62
    protected $template;
63
64
    /**
65
     * @var string
66
     */
67
    protected $file;
68
69
    /**
70
     * @var Meta
71
     */
72
    protected $meta;
73
74
    /**
75
     * @var Data
76
     */
77
    protected $data;
78
79
    /**
80
     * One of HTML, JSON or empty string
81
     * @var string
82
     */
83
    protected $render = 'HTML';
84
85
    /**
86
     * Constructor of Statement
87
     *
88
     * @param string $module
89
     * @param string $controller
90
     */
91 715
    public function __construct($module, $controller)
92
    {
93 715
        $this->module = $module;
94 715
        $this->controller = $controller;
95 715
        $this->template = $controller . '.phtml';
96
97
        // initial default helper path
98 715
        $this->addHelperPath(__DIR__ . '/Helper/');
99 715
    }
100
101
    /**
102
     * Check `Privilege`
103
     *
104
     * @throws ForbiddenException
105
     */
106 14
    public function checkPrivilege()
107
    {
108 14
        $privilege = $this->getMeta()->getPrivilege();
109 13
        if ($privilege && !Acl::isAllowed($this->module, $privilege)) {
110
            throw new ForbiddenException;
111
        }
112 13
    }
113
114
    /**
115
     * Check `Method`
116
     *
117
     * @throws NotAllowedException
118
     */
119 14
    public function checkMethod()
120
    {
121 14
        $methods = $this->getMeta()->getMethod();
122 13
        if ($methods && !in_array(Request::getMethod(), $methods)) {
123
            throw new NotAllowedException(implode(',', $methods));
124
        }
125 13
    }
126
127
    /**
128
     * Check `Accept`
129
     *
130
     * @throws NotAcceptableException
131
     */
132 14
    public function checkAccept()
133
    {
134
        // all ok for CLI
135 14
        if (PHP_SAPI === 'cli') {
136 14
            return;
137
        }
138
139
        $allowAccept = $this->getMeta()->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 13
    public function run(array $params = [])
183
    {
184
        // initial variables for use inside controller
185 13
        $module = $this->module;
186 13
        $controller = $this->controller;
187
188 13
        $cacheKey = "data.$module.$controller." . md5(http_build_query($params));
189
190 13
        $cacheTime = $this->getMeta()->getCache();
191
192 13
        if ($cacheTime && $cached = Cache::get($cacheKey)) {
193
            $this->data = $cached;
194
            return $cached;
195
        }
196
197
        /**
198
         * @var \closure $controllerClosure
199
         */
200 13
        $controllerClosure = include $this->getFile();
201
202 13
        if (!is_callable($controllerClosure)) {
203
            throw new ControllerException("Controller is not callable '{$module}/{$controller}'");
204
        }
205
206
        // process params
207 13
        $params = $this->getMeta()->params($params);
208
209
        // call Closure or Controller
210 13
        $result = $controllerClosure(...$params);
211
212
        // switch statement for result of Closure run
213
        switch (true) {
214 11
            case ($result === false):
215
                // return "false" is equal to disable view and layout
216 5
                $this->disableLayout();
217 5
                $this->disableView();
218 5
                break;
219 6
            case is_string($result):
220
                // return string variable is equal to change view template
221
                $this->template = $result;
222
                break;
223 6
            case is_array($result):
224
                // return associative array is equal to setup view data
225 3
                $this->getData()->setFromArray($result);
226 3
                break;
227 3
            case ($result instanceof Controller):
228
                $this->getData()->setFromArray($result->getData()->toArray());
229
                break;
230
        }
231
232 11
        if ($cacheTime) {
233
            Cache::set(
234
                $cacheKey,
235
                $this->getData(),
236
                $cacheTime,
237
                ['system', 'data', Cache::prepare("$module.$controller")]
238
            );
239
        }
240
241 11
        return $this->getData();
242
    }
243
244
    /**
245
     * Setup controller file
246
     *
247
     * @return void
248
     * @throws ControllerException
249
     */
250 715
    protected function setFile()
251
    {
252 715
        $path = Application::getInstance()->getPath();
253 715
        $file = "$path/modules/{$this->module}/controllers/{$this->controller}.php";
254
255 715
        if (!file_exists($file)) {
256 3
            throw new ControllerException("Controller file not found '{$this->module}/{$this->controller}'", 404);
257
        }
258
259 715
        $this->file = $file;
260 715
    }
261
262
    /**
263
     * Get controller file path
264
     * @return string
265
     */
266 715
    protected function getFile() // : string
267
    {
268 715
        if (!$this->file) {
269 715
            $this->setFile();
270
        }
271 715
        return $this->file;
272
    }
273
274
    /**
275
     * Retrieve reflection for anonymous function
276
     * @return void
277
     * @throws \Bluz\Common\Exception\ComponentException
278
     */
279 715
    protected function setMeta()
280
    {
281
        // cache for reflection data
282 715
        $cacheKey = "meta.{$this->module}.{$this->controller}";
283
284 715
        if (!$meta = Cache::get($cacheKey)) {
285 715
            $meta = new Meta($this->getFile());
286 715
            $meta->process();
287
288 715
            Cache::set(
289
                $cacheKey,
290
                $meta,
291 715
                Cache::TTL_NO_EXPIRY,
292 715
                ['system', 'meta', Cache::prepare($this->module . '.' . $this->controller)]
293
            );
294
        }
295 715
        $this->meta = $meta;
296 715
    }
297
    
298
    /**
299
     * Get meta information
300
     * @return Meta
301
     */
302 715
    public function getMeta()
303
    {
304 715
        if (!$this->meta) {
305 715
            $this->setMeta();
306
        }
307 715
        return $this->meta;
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 11
    public function getData()
328
    {
329 11
        if (!$this->data) {
330 11
            $this->data = new Data();
331
        }
332 11
        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 2
    public function __toString()
351
    {
352 2
        if (!$this->template) {
353
            return '';
354
        }
355
356
        try {
357
            // $view for use in closure
358 2
            $view = new View();
359
360 2
            $path = Application::getInstance()->getPath();
361
362
            // setup additional helper path
363 2
            $view->addHelperPath($path . '/layouts/helpers');
364
365
            // setup additional partial path
366 2
            $view->addPartialPath($path . '/layouts/partial');
367
368
            // setup default path
369 2
            $view->setPath($path . '/modules/' . $this->module . '/views');
370
371
            // setup template
372 2
            $view->setTemplate($this->template);
373
374
            // setup data
375 2
            $view->setFromArray($this->getData()->toArray());
376 2
            return $view->render();
377
        } catch (\Exception $e) {
378
            // save log
379
            Logger::error($e->getMessage());
380
            return '';
381
        }
382
    }
383
}
384