Response::send()   B
last analyzed

Complexity

Conditions 10
Paths 10

Size

Total Lines 57
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 24.578

Importance

Changes 8
Bugs 0 Features 0
Metric Value
cc 10
eloc 39
c 8
b 0
f 0
nc 10
nop 0
dl 0
loc 57
ccs 18
cts 38
cp 0.4737
crap 24.578
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Bluz Framework Component
5
 *
6
 * @copyright Bluz PHP Team
7
 * @link      https://github.com/bluzphp/framework
8
 */
9
10
declare(strict_types=1);
11
12
namespace Bluz\Response;
13
14
use Bluz\Common\Options;
15
use Bluz\Controller\Controller;
16
use Bluz\Http\StatusCode;
17
use Bluz\Layout\Layout;
0 ignored issues
show
Bug introduced by
The type Bluz\Layout\Layout was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use Bluz\Proxy\Messages;
19
use DateTime;
20
use InvalidArgumentException;
21
use Laminas\Diactoros\Response\EmptyResponse;
0 ignored issues
show
Bug introduced by
The type Laminas\Diactoros\Response\EmptyResponse was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Laminas\Diactoros\Response\HtmlResponse;
0 ignored issues
show
Bug introduced by
The type Laminas\Diactoros\Response\HtmlResponse was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
23
use Laminas\Diactoros\Response\JsonResponse;
0 ignored issues
show
Bug introduced by
The type Laminas\Diactoros\Response\JsonResponse was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use Laminas\Diactoros\Response\RedirectResponse;
0 ignored issues
show
Bug introduced by
The type Laminas\Diactoros\Response\RedirectResponse was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
0 ignored issues
show
Bug introduced by
The type Laminas\HttpHandlerRunner\Emitter\SapiEmitter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
27
/**
28
 * Response Container
29
 *
30
 * @package  Bluz\Response
31
 * @author   Anton Shevchuk
32
 * @link     https://github.com/bluzphp/framework/wiki/Response
33
 */
34
class Response
35
{
36
    use Options;
37
38
    /**
39
     * @var string HTTP protocol version
40
     */
41
    protected $protocol = '1.1';
42
43
    /**
44
     * @var integer response code equal to HTTP status codes
45
     */
46
    protected $code = StatusCode::OK;
47
48
    /**
49
     * @var string|null HTTP Phrase
50
     */
51
    protected $phrase;
52
53
    /**
54
     * @var array list of headers
55
     */
56
    protected $headers = [];
57
58
    /**
59
     * @var array list of cookies
60
     */
61
    protected $cookies = [];
62
63
    /**
64
     * @var Controller
65
     */
66
    protected $body;
67
68
    /**
69
     * @var string CLI|HTML|JSON|FILE
70
     */
71
    protected $type = 'HTML';
72
73
    /**
74
     * send
75 1
     */
76
    public function send(): void
77 1
    {
78
        $body = $this->getBody();
79 1
80
        $this->sendCookies();
81
82 1
        switch (true) {
83
            case 'CLI' === $this->type:
84
                // no CLI response
85 1
                return;
86 1
            case null === $body:
87
            case StatusCode::NO_CONTENT === $this->getStatusCode():
88
                $response = new EmptyResponse($this->getStatusCode(), $this->getHeaders());
89 1
                break;
90 1
            case StatusCode::MOVED_PERMANENTLY === $this->getStatusCode():
91
            case StatusCode::FOUND === $this->getStatusCode():
92
                $response = new RedirectResponse(
93
                    $this->getHeader('Location'),
94
                    $this->getStatusCode(),
95
                    $this->getHeaders()
96
                );
97 1
                break;
98
            case 'JSON' === $this->type:
99
                // JSON response
100
                // setup messages
101
                if (Messages::count()) {
102
                    $this->setHeader('Bluz-Notify', json_encode(Messages::popAll()));
103
                }
104
105
                // encode body data to JSON
106
                $response = new JsonResponse(
107
                    $body,
108
                    $this->getStatusCode(),
109
                    $this->getHeaders()
110
                );
111 1
                break;
112
            case 'FILE' === $this->type:
113
                // File attachment
114
                $response = new AttachmentResponse(
115
                    $this->body->getData()->get('FILE'),
116
                    $this->getStatusCode(),
117
                    $this->getHeaders()
118
                );
119 1
                break;
120
            case 'HTML' === $this->type:
121
            default:
122 1
                // HTML response
123 1
                $response = new HtmlResponse(
124 1
                    (string)$body,
125 1
                    $this->getStatusCode(),
126
                    $this->getHeaders()
127 1
                );
128
                break;
129
        }
130 1
131 1
        $emitter = new SapiEmitter();
132 1
        $emitter->emit($response);
133
    }
134
135
    /**
136
     * Get response type
137
     *
138
     * @return string
139 3
     */
140
    public function getType(): string
141 3
    {
142
        return $this->type;
143
    }
144
145
    /**
146
     * Set Response Type, one of JSON, HTML or CLI
147
     *
148
     * @param string $type
149 3
     */
150
    public function setType(string $type): void
151
    {
152 3
        // switch statement by content type
153 3
        switch ($type) {
154 2
            case 'JSON':
155 2
                $this->setHeader('Content-Type', 'application/json');
156 1
                break;
157 1
            case 'CLI':
158
            case 'FILE':
159
            case 'HTML':
160 1
            default:
161
                break;
162
        }
163 3
164 3
        $this->type = $type;
165
    }
166
167
168
    /**
169
     * Gets the HTTP protocol version as a string
170
     *
171
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
172
     *
173
     * @return string HTTP protocol version.
174 1
     */
175
    public function getProtocolVersion(): string
176 1
    {
177
        return $this->protocol;
178
    }
179
180
    /**
181
     * Gets the response Status-Code
182
     *
183
     * The Status-Code is a 3-digit integer result code of the server's attempt
184
     * to understand and satisfy the request.
185
     *
186
     * @return integer status code.
187 5
     */
188
    public function getStatusCode(): int
189 5
    {
190
        return $this->code;
191
    }
192
193
    /**
194
     * Sets the status code of this response
195
     *
196
     * @param integer $code the 3-digit integer result code to set.
197
     *
198
     * @return void
199 5
     */
200
    public function setStatusCode(int $code): void
201 5
    {
202 5
        $this->code = $code;
203
    }
204
205
    /**
206
     * Gets the response Reason-Phrase, a short textual description of the Status-Code
207
     *
208
     * Because a Reason-Phrase is not a required element in response
209
     * Status-Line, the Reason-Phrase value MAY be null. Implementations MAY
210
     * choose to return the default RFC 2616 recommended reason phrase for the
211
     * response's Status-Code.
212
     *
213
     * @return string|null reason phrase, or null if unknown.
214 2
     */
215
    public function getReasonPhrase(): ?string
216 2
    {
217
        return $this->phrase;
218
    }
219
220
    /**
221
     * Sets the Reason-Phrase of the response
222
     *
223
     * If no Reason-Phrase is specified, implementations MAY choose to default
224
     * to the RFC 2616 recommended reason phrase for the response's Status-Code.
225
     *
226
     * @param string $phrase the Reason-Phrase to set.
227 1
     */
228
    public function setReasonPhrase(string $phrase): void
229 1
    {
230 1
        $this->phrase = $phrase;
231
    }
232
233
    /**
234
     * Retrieve a header by the given case-insensitive name as a string
235
     *
236
     * This method returns all of the header values of the given
237
     * case-insensitive header name as a string concatenated together using
238
     * a comma.
239
     *
240
     * @param string $header case-insensitive header name.
241
     *
242
     * @return string
243 15
     */
244
    public function getHeader(string $header): string
245 15
    {
246 13
        if ($this->hasHeader($header)) {
247
            return implode(', ', $this->headers[$header]);
248 6
        }
249
        return '';
250
    }
251
252
    /**
253
     * Retrieves a header by the given case-insensitive name as an array of strings
254
     *
255
     * @param string $header Case-insensitive header name.
256
     *
257
     * @return string[]
258 1
     */
259
    public function getHeaderAsArray(string $header): array
260 1
    {
261 1
        if ($this->hasHeader($header)) {
262
            return $this->headers[$header];
263 1
        }
264
        return [];
265
    }
266
267
    /**
268
     * Checks if a header exists by the given case-insensitive name
269
     *
270
     * @param string $header case-insensitive header name.
271
     *
272
     * @return bool returns true if any header names match the given header
273
     *              name using a case-insensitive string comparison. Returns false if
274
     *              no matching header name is found in the message.
275 14
     */
276
    public function hasHeader(string $header): bool
277 14
    {
278
        return isset($this->headers[$header]);
279
    }
280
281
    /**
282
     * Sets a header, replacing any existing values of any headers with the
283
     * same case-insensitive name
284
     *
285
     * The header name is case-insensitive. The header values MUST be a string
286
     * or an array of strings.
287
     *
288
     * @param string $header header name
289
     * @param int|int[]|string|string[] $value  header value(s)
290
     *
291
     * @return void
292 15
     */
293
    public function setHeader(string $header, $value): void
294 15
    {
295 15
        $this->headers[$header] = (array)$value;
296
    }
297
298
    /**
299
     * Appends a header value for the specified header
300
     *
301
     * Existing values for the specified header will be maintained. The new
302
     * value will be appended to the existing list.
303
     *
304
     * @param string $header header name to add
305
     * @param string|int $value  value of the header
306
     *
307
     * @return void
308 1
     */
309
    public function addHeader(string $header, $value): void
310 1
    {
311 1
        if ($this->hasHeader($header)) {
312
            $this->headers[$header][] = $value;
313 1
        } else {
314
            $this->setHeader($header, $value);
315 1
        }
316
    }
317
318
    /**
319
     * Remove a specific header by case-insensitive name.
320
     *
321
     * @param string $header HTTP header to remove
322
     *
323
     * @return void
324 1
     */
325
    public function removeHeader(string $header): void
326 1
    {
327 1
        unset($this->headers[$header]);
328
    }
329
330
    /**
331
     * Gets all message headers
332
     *
333
     * The keys represent the header name as it will be sent over the wire, and
334
     * each value is an array of strings associated with the header.
335
     *
336
     *     // Represent the headers as a string
337
     *     foreach ($message->getHeaders() as $name => $values) {
338
     *         echo $name . ": " . implode(", ", $values);
339
     *     }
340
     *
341
     * @return array returns an associative array of the message's headers.
342 2
     */
343
    public function getHeaders(): array
344 2
    {
345
        return $this->headers;
346
    }
347
348
    /**
349
     * Sets headers, replacing any headers that have already been set on the message
350
     *
351
     * The array keys MUST be a string. The array values must be either a
352
     * string or an array of strings.
353
     *
354
     * @param  array $headers Headers to set.
355
     *
356
     * @return void
357 1
     */
358
    public function setHeaders(array $headers): void
359 1
    {
360 1
        $this->headers = $headers;
361
    }
362
363
    /**
364
     * Merges in an associative array of headers.
365
     *
366
     * Each array key MUST be a string representing the case-insensitive name
367
     * of a header. Each value MUST be either a string or an array of strings.
368
     * For each value, the value is appended to any existing header of the same
369
     * name, or, if a header does not already exist by the given name, then the
370
     * header is added.
371
     *
372
     * @param  array $headers Associative array of headers to add to the message
373
     *
374
     * @return void
375 1
     */
376
    public function addHeaders(array $headers): void
377 1
    {
378 1
        $this->headers = array_merge_recursive($this->headers, $headers);
379
    }
380
381
    /**
382
     * Remove all headers
383
     *
384
     * @return void
385 6
     */
386
    public function removeHeaders(): void
387 6
    {
388 6
        $this->headers = [];
389
    }
390
391
    /**
392
     * Set response body
393
     *
394
     * @param  mixed $body
395
     *
396
     * @return void
397 9
     */
398
    public function setBody($body): void
399 9
    {
400 9
        $this->body = $body;
401
    }
402
403
    /**
404
     * Get response body
405
     *
406
     * @return Controller|Layout
407 3
     */
408
    public function getBody()
409 3
    {
410
        return $this->body;
411
    }
412
413
    /**
414
     * Clear response body
415
     *
416
     * @return void
417 5
     */
418
    public function clearBody(): void
419 5
    {
420 5
        $this->body = null;
421
    }
422
423
    /**
424
     * Set Cookie
425
     *
426
     * @param string $name
427
     * @param string $value
428
     * @param int|string|DateTime $expire
429
     * @param string $path
430
     * @param string $domain
431
     * @param bool $secure
432
     * @param bool $httpOnly
433
     *
434
     * @return void
435
     * @throws InvalidArgumentException
436 5
     */
437
    public function setCookie(
438
        string $name,
439
        string $value = '',
440
        $expire = 0,
441
        string $path = '/',
442
        string $domain = '',
443
        bool $secure = false,
444
        bool $httpOnly = false
445
    ): void {
446 5
        // from PHP source code
447 1
        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
448
            throw new InvalidArgumentException('The cookie name contains invalid characters.');
449
        }
450 4
451 1
        if (empty($name)) {
452
            throw new InvalidArgumentException('The cookie name cannot be empty.');
453
        }
454
455 3
        // convert expiration time to a Unix timestamp
456 1
        if ($expire instanceof DateTime) {
457 2
            $expire = $expire->format('U');
458 1
        } elseif (!is_numeric($expire)) {
459 1
            $expire = strtotime($expire);
460 1
            if (false === $expire || -1 === $expire) {
461
                throw new InvalidArgumentException('The cookie expiration time is not valid.');
462
            }
463
        }
464 2
465 2
        $this->cookies[$name] = [
466 2
            'name' => $name,
467 2
            'value' => $value,
468 2
            'expire' => $expire,
469 2
            'path' => $path,
470 2
            'domain' => $domain,
471 2
            'secure' => $secure,
472
            'httpOnly' => $httpOnly
473 2
        ];
474
    }
475
476
    /**
477
     * Get Cookie by name
478
     *
479
     * @param string $name
480
     *
481
     * @return array|null
482 2
     */
483
    public function getCookie(string $name): ?array
484 2
    {
485
        return $this->cookies[$name] ?? null;
486
    }
487
488
    /**
489
     * Process Cookies to Header
490
     *
491
     *   Set-Cookie: <name>=<value>[; <name>=<value>]...
492
     *   [; expires=<date>][; domain=<domain_name>]
493
     *   [; path=<some_path>][; secure][; httponly]
494
     *
495
     * @return void
496 1
     */
497
    protected function sendCookies(): void
498 1
    {
499
        foreach ($this->cookies as $cookie) {
500
            setcookie(...array_values($cookie));
501 1
        }
502
    }
503
}
504