Passed
Pull Request — master (#17)
by Mihail
15:10
created

ServerResponse::setStatus()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 10
rs 10
ccs 5
cts 5
cp 1
crap 4
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use Koded\Http\Interfaces\{HttpStatus, Response};
16
use InvalidArgumentException;
17
use JsonSerializable;
18
use function implode;
19
use function sprintf;
20
use function strtoupper;
21
22
/**
23
 * Class ServerResponse
24
 *
25
 */
26
class ServerResponse implements Response, JsonSerializable
27
{
28
    use HeaderTrait, MessageTrait, CookieTrait, JsonSerializeTrait;
29
30
    private const E_CLIENT_RESPONSE_SEND = 'Cannot send the client response.';
31
    private const E_INVALID_STATUS_CODE  = 'Invalid status code %s, expected range between [100-599]';
32
33
    protected static array $skippableStatusCodes = [
34
        //100 => true, // @version 4.0.0
35
        101 => true,
36
        //102 => true, // @version 4.0.0
37
        103 => true, // @version 4.0.0
38
        204 => true,
39
        205 => true, // @version 4.0.0
40 51
        304 => true,
41
    ];
42 51
43 51
    protected int    $statusCode   = HttpStatus::OK;
44 51
    protected string $reasonPhrase = 'OK';
45 51
46
    /**
47 33
     * ServerResponse constructor.
48
     *
49 33
     * @param mixed $content    [optional]
50
     * @param int   $statusCode [optional]
51
     * @param array $headers    [optional]
52 6
     */
53
    public function __construct(
54 6
        mixed $content = '',
55
        int $statusCode = HttpStatus::OK,
56
        array $headers = [])
57 9
    {
58
        $this->setStatus($this, $statusCode);
59 9
        $this->setHeaders($headers);
60
        $this->stream = create_stream($content);
61
    }
62 2
63
    public function getStatusCode(): int
64 2
    {
65
        return $this->statusCode;
66
    }
67 4
68
    public function withStatus(int $code, string $reasonPhrase = ''): static
69 4
    {
70
        return $this->setStatus(clone $this, $code, $reasonPhrase);
71 4
    }
72 1
73
    public function getReasonPhrase(): string
74
    {
75
        return $this->reasonPhrase;
76 3
    }
77 2
78
    public function getContentType(): string
79
    {
80
        return $this->getHeaderLine('Content-Type') ?: 'text/html';
81 3
    }
82 3
83
    public function sendHeaders(): void
84
    {
85 3
        $this->prepareResponse();
86
        if (false === headers_sent()) {
87
            foreach ($this->getHeaders() as $name => $values) {
88 51
                header($name . ':' . implode(',', (array)$values), false, $this->statusCode);
89
            }
90 51
            // Status header
91 1
            header(sprintf('HTTP/%s %d %s',
92 1
                $this->getProtocolVersion(),
93
                $this->getStatusCode(),
94
                $this->getReasonPhrase()),
95
                true,
96 51
                $this->statusCode);
97 51
        }
98
    }
99 51
100
    public function sendBody(): string
101
    {
102 4
        try {
103
            return (string)$this->stream;
104 4
        } finally {
105 1
            $this->stream->close();
106 1
        }
107 1
    }
108
109 1
    public function send(): string
110
    {
111
        $this->sendHeaders();
112 3
        return $this->sendBody();
113 3
    }
114
115
    protected function setStatus(ServerResponse $instance, int $statusCode, string $reasonPhrase = ''): ServerResponse
116 3
    {
117 2
        if ($statusCode < 100 || $statusCode > 599) {
118
            throw new InvalidArgumentException(
119
                sprintf(self::E_INVALID_STATUS_CODE, $statusCode), HttpStatus::UNPROCESSABLE_ENTITY
120 3
            );
121 1
        }
122
        $instance->statusCode   = $statusCode;
123 3
        $instance->reasonPhrase = $reasonPhrase ?: HttpStatus::CODE[$statusCode];
124
        return $instance;
125
    }
126
127
    protected function prepareResponse(): void
128
    {
129
        if (isset(static::$skippableStatusCodes[$this->statusCode])) {
130
            $this->stream = create_stream(null);
131
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
132
            unset($this->headersMap['content-type'], $this->headers['Content-Type']);
133
            return;
134
        }
135
        if ($size = $this->stream->getSize()) {
136
            $this->normalizeHeader('Content-Length', (string)$size, true);
137
        }
138
        $method = strtoupper($_SERVER['REQUEST_METHOD'] ?? '');
139
        if ('HEAD' === $method || 'OPTIONS' === $method) {
140
            $this->stream = create_stream(null);
141
        }
142
        if ($this->hasHeader('Transfer-Encoding') || !$size) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $size of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
143
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
144
        }
145
    }
146
}
147