Completed
Push — v3 ( 4ecdc9...abfb2c )
by Mihail
06:27
created

ServerResponse.php (1 issue)

Severity
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 InvalidArgumentException;
16
use JsonSerializable;
17
use Koded\Http\Interfaces\{HttpStatus, Request, Response};
18
19
/**
20
 * Class ServerResponse
21
 *
22
 */
23
class ServerResponse implements Response, JsonSerializable
24
{
25
    use HeaderTrait, MessageTrait, CookieTrait, JsonSerializeTrait;
26
27
    private const E_CLIENT_RESPONSE_SEND = 'Cannot send the client response.';
28
    private const E_INVALID_STATUS_CODE  = 'Invalid status code %s, expected range between [100-599]';
29
30
    protected $statusCode   = HttpStatus::OK;
31
    protected $reasonPhrase = 'OK';
32
33
    /**
34
     * ServerResponse constructor.
35
     *
36
     * @param mixed $content    [optional]
37
     * @param int   $statusCode [optional]
38
     * @param array $headers    [optional]
39
     */
40
    public function __construct($content = '', int $statusCode = HttpStatus::OK, array $headers = [])
41
    {
42
        $this->setStatus($this, $statusCode);
43
        $this->setHeaders($headers);
44
        $this->stream = create_stream($content);
45
    }
46
47
    public function getStatusCode(): int
48
    {
49
        return $this->statusCode;
50
    }
51
52
    public function withStatus($code, $reasonPhrase = ''): Response
53
    {
54
        return $this->setStatus(clone $this, (int)$code, (string)$reasonPhrase);
55
    }
56
57
    public function getReasonPhrase(): string
58
    {
59
        return (string)$this->reasonPhrase;
60
    }
61
62
    public function getContentType(): string
63
    {
64
        return $this->getHeaderLine('Content-Type') ?: 'text/html';
65
    }
66
67
    public function sendHeaders(): void
68
    {
69
        $this->prepareResponse();
70
71
        if (false === headers_sent()) {
72
            foreach ($this->getHeaders() as $name => $values) {
73
                header($name . ':' . join(',', (array)$values), false, $this->statusCode);
0 ignored issues
show
The call to Koded\Http\header() has too many arguments starting with $name . ':' . join(',', (array)$values). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

73
                /** @scrutinizer ignore-call */ 
74
                header($name . ':' . join(',', (array)$values), false, $this->statusCode);

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. Please note the @ignore annotation hint above.

Loading history...
74
            }
75
            // Status header
76
            header(sprintf('HTTP/%s %d %s',
77
                $this->getProtocolVersion(),
78
                $this->getStatusCode(),
79
                $this->getReasonPhrase()),
80
                true,
81
                $this->statusCode);
82
        }
83
    }
84
85
    public function sendBody(): string
86
    {
87
        try {
88
            return (string)$this->stream;
89
        } finally {
90
            $this->stream->close();
91
        }
92
    }
93
94
    public function send(): string
95
    {
96
        $this->sendHeaders();
97
        return $this->sendBody();
98
    }
99
100
    protected function setStatus(ServerResponse $instance, int $statusCode, string $reasonPhrase = ''): ServerResponse
101
    {
102
        if ($statusCode < 100 || $statusCode > 599) {
103
            throw new InvalidArgumentException(
104
                sprintf(self::E_INVALID_STATUS_CODE, $statusCode), HttpStatus::UNPROCESSABLE_ENTITY
105
            );
106
        }
107
        $instance->statusCode   = (int)$statusCode;
108
        $instance->reasonPhrase = $reasonPhrase ? (string)$reasonPhrase : StatusCode::CODE[$statusCode];
109
        return $instance;
110
    }
111
112
    protected function prepareResponse(): void
113
    {
114
        if (in_array($this->getStatusCode(), [100, 101, 102, 204, 304])) {
115
            $this->stream = create_stream(null);
116
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
117
            unset($this->headersMap['content-type'], $this->headers['Content-Type']);
118
            return;
119
        }
120
        if ($size = $this->stream->getSize()) {
121
            $this->normalizeHeader('Content-Length', $size, true);
122
        }
123
        if (Request::HEAD === strtoupper($_SERVER['REQUEST_METHOD'] ?? '')) {
124
            $this->stream = create_stream(null);
125
        }
126
        if ($this->hasHeader('Transfer-Encoding') || !$size) {
127
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
128
        }
129
    }
130
}
131