Response   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 467
Duplicated Lines 0 %

Test Coverage

Coverage 82.95%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 105
c 10
b 0
f 0
dl 0
loc 467
ccs 107
cts 129
cp 0.8295
rs 8.64
wmc 47

24 Methods

Rating   Name   Duplication   Size   Complexity  
A setStatusCode() 0 3 1
A setReasonPhrase() 0 3 1
A addHeaders() 0 3 1
A removeHeaders() 0 3 1
A clearBody() 0 3 1
A setHeaders() 0 3 1
A setType() 0 15 5
A setBody() 0 3 1
A removeHeader() 0 3 1
B send() 0 57 10
A setHeader() 0 3 1
A hasHeader() 0 3 1
A getHeaders() 0 3 1
A getType() 0 3 1
A getReasonPhrase() 0 3 1
A getHeaderAsArray() 0 6 2
A getCookie() 0 3 1
A getHeader() 0 6 2
A getBody() 0 3 1
A getProtocolVersion() 0 3 1
A addHeader() 0 6 2
A sendCookies() 0 4 2
A getStatusCode() 0 3 1
B setCookie() 0 36 7

How to fix   Complexity   

Complex Class

Complex classes like Response often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Response, and based on these observations, apply Extract Interface, too.

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