Completed
Push — master ( f806ab...8def13 )
by Anton
9s
created

Controller::getFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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