Completed
Push — master ( f32a89...d65b07 )
by Mehmet
03:31
created

Response::sendResponse()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 40
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 0
cts 40
cp 0
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 37
nc 8
nop 0
crap 72
1
<?php
2
declare(strict_types=1);
3
4
namespace Selami\Foundation;
5
6
use Selami;
7
use Zend\Config\Config as ZendConfig;
8
use Psr\Container\ContainerInterface;
9
use Selami\View\ViewInterface;
10
use Selami\Stdlib\CaseConverter;
11
use Symfony\Component\HttpFoundation\Session\Session;
12
13
class Response
14
{
15
    private $config;
16
    private $container;
17
    private $view;
18
    private $session;
19
    /**
20
     * @var int
21
     */
22
    private $statusCode = 200;
23
    /**
24
     * @var array
25
     */
26
    private $headers = [];
27
    /**
28
     * @var array
29
     */
30
    private $cookies = [];
31
    /**
32
     * @var string
33
     */
34
    private $body = '';
35
36
    /**
37
     * @var array
38
     */
39
    private $data = [];
40
    /**
41
     * @var string
42
     */
43
    private $contentType = Selami\Router::HTML;
44
45
    /**
46
     * @var string
47
     */
48
    private $redirect;
49
    private $downloadFilePath;
50
    private $downloadFileName;
51
    private $customContentType;
52
53
    public function __construct(ContainerInterface $container)
54
    {
55
        $this->container = $container;
56
        $this->config = $container->get(ZendConfig::class);
57
    }
58
59
    private function checkTemplateFile($template, $type, $controller) : void
60
    {
61
        if (!file_exists($this->config->app->get('templates_dir', './templates') .'/'. $template)) {
62
            $message  = sprintf(
63
                '%s  template file not found! %s  needs a main template file at: %s',
64
                $type,
65
                $controller,
66
                $this->config['app_dir'] .'/'. $template
67
            );
68
            throw new \DomainException($message);
69
        }
70
    }
71
72
    public function setResponse(int $returnType, array $actionOutput, string $controller) : void
73
    {
74
        switch ($returnType) {
75
            case Selami\Router::HTML:
76
                $this->setRenderedResponse(Selami\Router::HTML, $actionOutput, $controller);
77
                break;
78
            case Selami\Router::JSON:
79
                $this->setJsonResponse($actionOutput);
80
                break;
81
            case Selami\Router::TEXT:
82
                $this->setRenderedResponse(Selami\Router::TEXT, $actionOutput, $controller);
83
                break;
84
            case Selami\Router::XML:
85
                $this->setRenderedResponse(Selami\Router::XML, $actionOutput, $controller);
86
                break;
87
            case Selami\Router::DOWNLOAD:
88
                $this->setDownloadResponse($actionOutput);
89
                break;
90
            case Selami\Router::CUSTOM:
91
                $this->setRenderedResponse(Selami\Router::CUSTOM, $actionOutput, $controller);
92
                break;
93
            case Selami\Router::REDIRECT:
94
                $this->setRedirectResponse($actionOutput);
95
                break;
96
        }
97
    }
98
99
    public function setRedirectResponse(array $actionOutput) : void
100
    {
101
        $this->contentType = Selami\Router::REDIRECT;
102
        if (isset($actionOutput['meta']['type']) && $actionOutput['meta']['type'] === Dispatcher::REDIRECT) {
103
            $this->contentType = Selami\Router::REDIRECT;
104
            $this->statusCode = $actionOutput['status'] ?? 302;
105
            $this->redirect = $actionOutput['meta']['redirect_url'];
106
        }
107
    }
108
    
109
    public function setDownloadResponse(array $actionOutput) : void
110
    {
111
        $this->contentType = Selami\Router::DOWNLOAD;
112
        if (isset($actionOutput['meta']['type']) ?? $actionOutput['meta']['type'] === Dispatcher::DOWNLOAD) {
113
            $statusCode = $actionOutput['status'] ?? 200;
114
            $this->statusCode = (int) $statusCode;
115
            $this->downloadFilePath = $actionOutput['meta']['download_file_path'];
116
            $this->downloadFileName = $actionOutput['meta']['download_file_name'] ?? date('Ymdhis');
117
        }
118
    }
119
120
    public function setJsonResponse(array $actionOutput) : void
121
    {
122
        $this->contentType = Selami\Router::JSON;
123
124
        if (!is_array($actionOutput)) {
125
            $actionOutput = ['status' => 500, 'error' => 'Internal Server Error'];
126
        }
127
        if (!isset($actionOutput['status'])) {
128
            $actionOutput['status'] = 200;
129
        }
130
        $status = (int) $actionOutput['status'];
131
        $this->statusCode = $status;
132
        $this->data = $actionOutput;
133
    }
134
135
    private function setRenderedResponse(int $returnType, array $actionOutput, string $controllerClass) : void
136
    {
137
        $this->useSession();
138
        $this->useView($this->container->get(ViewInterface::class));
139
        $this->view->addGlobal('defined', get_defined_constants(true)['user'] ?? []);
140
        $this->view->addGlobal('session', $this->session->all());
141
        $this->renderResponse($returnType, $actionOutput, $controllerClass);
142
    }
143
144
    private function renderResponse(int $returnType, array $actionOutput, string $controllerClass) : void
145
    {
146
        $paths = explode("\\", $controllerClass);
147
        $templateFile = array_pop($paths);
148
        $templateFolder = array_pop($paths);
149
        $template = CaseConverter::toSnakeCase($templateFolder) . '/' . CaseConverter::toSnakeCase($templateFile) . '.twig';
150
        $this->checkTemplateFile($template, 'Method\'s', $controllerClass);
151
        $actionOutput['data'] = $actionOutput['data'] ?? [];
152
        $output = [
153
            'status' => $actionOutput['status'] ?? 200,
154
            'meta' => $actionOutput['meta'] ?? [],
155
            'app' => null
156
        ];
157
        $output['app']['_content'] = $this->view->render($template, $actionOutput['data']);
158
        $mainTemplateName = $actionOutput['meta']['layout'] ?? 'default';
159
        $mainTemplate = '_' . CaseConverter::toSnakeCase($mainTemplateName) . '.twig';
160
        $this->checkTemplateFile($mainTemplate, 'Layout', $controllerClass);
161
        $this->contentType = $returnType;
0 ignored issues
show
Documentation Bug introduced by
The property $contentType was declared of type string, but $returnType is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
162
        if ($returnType === Selami\Router::CUSTOM) {
163
            $this->customContentType = $actionOutput['meta']['content_type'] ?? 'plain/text';
164
        }
165
        $this->body = $this->view->render($mainTemplate, $output);
166
    }
167
168
    public function notFound($status = 404, $returnType = Selami\Router::HTML, $message = 'Not Found') : void
169
    {
170
        if ($returnType === Selami\Router::JSON) {
171
            $this->body = ['status' => $status, 'message' => $message];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('status' => $status, 'message' => $message) of type array<string,integer|str...r","message":"string"}> is incompatible with the declared type string of property $body.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
172
        } else {
173
            $this->useView($this->container->get(ViewInterface::class));
174
            $notFoundTemplate = '_404.twig';
175
            $this->contentType = $returnType;
176
            $this->body = $this->view->render(
177
                $notFoundTemplate,
178
                ['message' => $message, 'status' => $status]
179
            );
180
        }
181
        $this->statusCode = $status;
182
    }
183
184
    private function useView(ViewInterface $view) : void
185
    {
186
        $this->view = $view;
187
    }
188
189
    private function useSession() : void
190
    {
191
        $this->session = $this->container->get(Session::class);
192
    }
193
194
    private function setHeaders() : void
195
    {
196
        $this->headers['X-Powered-By']      = 'r/selami';
197
        $this->headers['X-Frame-Options']   = 'SAMEORIGIN';
198
        $this->headers['X-XSS-Protection']  = '1; mode=block';
199
        $this->headers['Strict-Transport-Security'] = 'max-age=31536000';
200
        if (array_key_exists('headers', $this->config) && is_array($this->config['headers'])) {
201
            foreach ($this->config['headers'] as $header => $value) {
202
                $this->headers[$header] = $value;
203
            }
204
        }
205
    }
206
207
    public function getResponse() : array
208
    {
209
        $headers = $this->config['headers'] ?? null;
210
        $this->setHeaders($headers);
0 ignored issues
show
Unused Code introduced by
The call to Response::setHeaders() has too many arguments starting with $headers.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
211
        return [
212
            'statusCode'    => $this->statusCode,
213
            'headers'       => $this->headers,
214
            'cookies'       => $this->cookies,
215
            'body'          => (string) $this->body,
216
            'data'          => $this->data,
217
            'contentType'   => $this->contentType,
218
            'redirect'      => $this->redirect
219
        ];
220
    }
221
222
    public function sendResponse() : void
223
    {
224
        $response = new Selami\Http\Response();
225
        $response->setHeaders($this->headers);
226
        $response->setStatusCode($this->statusCode);
227
        switch ($this->contentType) {
228
            case Selami\Router::REDIRECT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
229
                $response->setOutputType(Selami\Router::REDIRECT);
230
                $response->setRedirect($this->redirect);
231
                break;
232
            case Selami\Router::CUSTOM:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
233
                $response->setOutputType(Selami\Router::CUSTOM);
234
                $response->setCustomContentType($this->customContentType);
235
                $response->setBody($this->body);
236
                break;
237
            case Selami\Router::DOWNLOAD:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
238
                $response->setOutputType(Selami\Router::DOWNLOAD);
239
                $response->setDownloadFileName($this->downloadFileName);
240
                $response->setDownloadFilePath($this->downloadFilePath);
241
                break;
242
            case Selami\Router::JSON:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
243
                $response->setOutputType(Selami\Router::JSON);
244
                $response->setData($this->data);
245
                break;
246
            case Selami\Router::TEXT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
247
                $response->setOutputType(Selami\Router::TEXT);
248
                $response->setBody($this->body);
249
                break;
250
            case Selami\Router::XML:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
251
                $response->setOutputType(Selami\Router::XML);
252
                $response->setBody($this->body);
253
                break;
254
            case Selami\Router::HTML:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
255
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
256
                $response->setOutputType(Selami\Router::HTML);
257
                $response->setBody($this->body);
258
                break;
259
        }
260
        $response->send();
261
    }
262
}
263