Completed
Pull Request — master (#2)
by René
05:31 queued 03:25
created

Controller::getViewTemplate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 10
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 6
1
<?php
2
declare(strict_types = 1);
3
4
namespace Zortje\MVC\Controller;
5
6
use Zortje\MVC\Configuration\Configuration;
7
use Zortje\MVC\Controller\Exception\ControllerActionNonexistentException;
8
use Zortje\MVC\Controller\Exception\ControllerActionPrivateInsufficientAuthenticationException;
9
use Zortje\MVC\Controller\Exception\ControllerActionProtectedInsufficientAuthenticationException;
10
use Zortje\MVC\Storage\Cookie\Cookie;
11
use Zortje\MVC\User\User;
12
use Zortje\MVC\View\Render\HtmlRender;
13
14
/**
15
 * Class Controller
16
 *
17
 * @package Zortje\MVC\Controller
18
 */
19
class Controller
20
{
21
22
    /**
23
     * Controller action is publicly accessible
24
     */
25
    const ACTION_PUBLIC = 0;
26
27
    /**
28
     * Controller action requires authentication
29
     * Will redirect to sign in page if not authenticated
30
     */
31
    const ACTION_PROTECTED = 1;
32
33
    /**
34
     * Controller action requires authentication
35
     * Will result in an 404 if not authenticated
36
     */
37
    const ACTION_PRIVATE = 2;
38
39
    /**
40
     * @var array Controller action access rules
41
     */
42
    protected $access = [];
43
44
    /**
45
     * @var \PDO PDO
46
     */
47
    protected $pdo;
48
49
    /**
50
     * @var Configuration
51
     */
52
    protected $configuration;
53
54
    /**
55
     * @var Cookie
56
     */
57
    protected $cookie;
58
59
    /**
60
     * @var User|null User
61
     */
62
    protected $user;
63
64
    /**
65
     * @var string Controller action
66
     */
67
    protected $action;
68
69
    /**
70
     * @var array View variables
71
     */
72
    protected $variables = [];
73
74
    /**
75
     * @var bool Should render view for controller action
76
     */
77
    protected $render = true;
78
79
    /**
80
     * @var string File path for layout template file
81
     */
82
    protected $layout;
83
84
    /**
85
     * @var string File path for view template file
86
     */
87
    protected $view;
88
89
    /**
90
     * @var array Headers for output
91
     *
92
     * @todo JSON content type: `Content-Type: application/javascript; charset=utf-8`
93
     */
94
    protected $headers = [
95
        'content-type' => 'Content-Type: text/html; charset=utf-8'
96
    ];
97
98
    /**
99
     * Controller constructor.
100
     *
101
     * @param \PDO          $pdo
102
     * @param Configuration $configuration
103
     * @param Cookie        $cookie
104
     * @param User|null     $user
105
     */
106 View Code Duplication
    public function __construct(\PDO $pdo, Configuration $configuration, Cookie $cookie, User $user = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
    {
108
        $this->pdo           = $pdo;
109
        $this->configuration = $configuration;
110
        $this->cookie        = $cookie;
111
        $this->user          = $user;
112
    }
113
114
    /**
115
     * @return string Controller name without namespace
116
     */
117
    public function getShortName(): string
118
    {
119
        return str_replace('Controller', null, (new \ReflectionClass($this))->getShortName());
120
    }
121
122
    /**
123
     * @param string $action Controller action
124
     *
125
     * @throws ControllerActionNonexistentException
126
     * @throws ControllerActionPrivateInsufficientAuthenticationException
127
     * @throws ControllerActionProtectedInsufficientAuthenticationException
128
     */
129
    public function setAction(string $action)
130
    {
131
        /**
132
         * Check if method exists and that access has been defined
133
         */
134
        if (!method_exists($this, $action) || !isset($this->access[$action])) {
135
            throw new ControllerActionNonexistentException([get_class($this), $action]);
136
        }
137
138
        /**
139
         * Check controller action access level if user is not authenticated
140
         */
141
        if (!$this->user) {
142
            if ($this->access[$action] === self::ACTION_PRIVATE) {
143
                throw new ControllerActionPrivateInsufficientAuthenticationException([get_class($this), $action]);
144
            } elseif ($this->access[$action] === self::ACTION_PROTECTED) {
145
                throw new ControllerActionProtectedInsufficientAuthenticationException([get_class($this), $action]);
146
            }
147
        }
148
149
        /**
150
         * Set controller action
151
         */
152
        $this->action = $action;
153
    }
154
155
    /**
156
     * Call action
157
     *
158
     * @return array<string,array|string> Headers and output if render is enabled, otherwise FALSE
159
     *
160
     * @throws \LogicException If controller action is not set
161
     */
162
    public function callAction(): array
163
    {
164
        if (!isset($this->action)) {
165
            throw new \LogicException('Controller action must be set before being called');
166
        }
167
168
        /**
169
         * Before controller action hook
170
         */
171
        $this->beforeAction();
172
173
        /**
174
         * Call controller action
175
         */
176
        $action = $this->action;
177
178
        $this->$action();
0 ignored issues
show
Security Code Execution introduced by
$action can contain request data and is used in code execution context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
179
180
        /**
181
         * After controller action hook
182
         */
183
        $this->afterAction();
184
185
        /**
186
         * Render view
187
         */
188
        if ($this->render) {
189
            $render = new HtmlRender($this->variables);
190
191
            $output = $render->render(['_view' => $this->getViewTemplate(), '_layout' => $this->getLayoutTemplate()]);
192
193
            return [
194
                'headers'      => $this->headers,
195
                'output'       => $output
196
            ];
197
        }
198
199
        return [];
200
    }
201
202
    /**
203
     * Before controller action hook
204
     *
205
     * Called right before controller action is called
206
     */
207
    protected function beforeAction()
208
    {
209
    }
210
211
    /**
212
     * After controller action hook
213
     *
214
     * Called right after controller action is called, but before rendering of the view
215
     */
216
    protected function afterAction()
217
    {
218
    }
219
220
    /**
221
     * Set view variable
222
     *
223
     * @param string $variable
224
     * @param mixed  $value
225
     */
226
    protected function set(string $variable, $value)
227
    {
228
        $this->variables[$variable] = $value;
229
    }
230
231
    /**
232
     * Get layout template
233
     *
234
     * @return string Layout template file path
235
     */
236
    protected function getLayoutTemplate(): string
237
    {
238
        $layout = $this->layout;
239
240
        if (empty($layout)) {
241
            $layout = 'View/Layout/default';
242
        }
243
244
        return "{$this->configuration->get('App.Path')}$layout.layout";
245
    }
246
247
    /**
248
     * Get view template
249
     *
250
     * @return string View template file path
251
     */
252
    protected function getViewTemplate(): string
253
    {
254
        $view = $this->view;
255
256
        if (empty($view)) {
257
            $view = sprintf('View/%s/%s', $this->getShortName(), $this->action);
258
        }
259
260
        return "{$this->configuration->get('App.Path')}$view.view";
261
    }
262
263
    /**
264
     * Set response code
265
     *
266
     * Supports 200 OK, 403 Forbidden, 404 Not Found & 500 Internal Server Error
267
     *
268
     * @param int $code HTTP response code
269
     *
270
     * @throws \InvalidArgumentException If unsupported code is provided
271
     */
272
    protected function setResponseCode(int $code)
273
    {
274
        switch ($code) {
275
            case 200:
276
                $text = 'OK';
277
                break;
278
279
            case 403:
280
                $text = 'Forbidden';
281
                break;
282
283
            case 404:
284
                $text = 'Not Found';
285
                break;
286
287
            case 500:
288
                $text = 'Internal Server Error';
289
                break;
290
291
            default:
292
                throw new \InvalidArgumentException("HTTP status '$code' is not implemented");
293
        }
294
295
        /**
296
         * Set header
297
         */
298
        // @todo test that running response code multiple times only results in one response code header
299
        $this->headers['response_code'] = "HTTP/1.1 $code $text";
300
    }
301
}
302