Passed
Push — master ( c01d71...667403 )
by Anatoly
04:35
created
src/UploadedFile.php 1 patch
Indentation   +135 added lines, -135 removed lines patch added patch discarded remove patch
@@ -40,139 +40,139 @@
 block discarded – undo
40 40
 
41 41
 class UploadedFile implements UploadedFileInterface
42 42
 {
43
-    /**
44
-     * @link https://www.php.net/manual/en/features.file-upload.errors.php
45
-     *
46
-     * @var array<int, non-empty-string>
47
-     */
48
-    public const UPLOAD_ERRORS = [
49
-        UPLOAD_ERR_OK         => 'No error',
50
-        UPLOAD_ERR_INI_SIZE   => 'Uploaded file exceeds the upload_max_filesize directive in the php.ini',
51
-        UPLOAD_ERR_FORM_SIZE  => 'Uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form',
52
-        UPLOAD_ERR_PARTIAL    => 'Uploaded file was only partially uploaded',
53
-        UPLOAD_ERR_NO_FILE    => 'No file was uploaded',
54
-        UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary directory',
55
-        UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
56
-        UPLOAD_ERR_EXTENSION  => 'File upload was stopped by a PHP extension',
57
-    ];
58
-
59
-    private ?StreamInterface $stream;
60
-    private ?int $size;
61
-    private int $errorCode;
62
-    private string $errorMessage;
63
-    private ?string $clientFilename;
64
-    private ?string $clientMediaType;
65
-    private bool $isMoved = false;
66
-
67
-    public function __construct(
68
-        ?StreamInterface $stream,
69
-        ?int $size = null,
70
-        int $error = UPLOAD_ERR_OK,
71
-        ?string $clientFilename = null,
72
-        ?string $clientMediaType = null
73
-    ) {
74
-        $this->stream = $stream;
75
-        $this->size = $size;
76
-        $this->errorCode = $error;
77
-        $this->errorMessage = self::UPLOAD_ERRORS[$error] ?? 'Unknown file upload error';
78
-        $this->clientFilename = $clientFilename;
79
-        $this->clientMediaType = $clientMediaType;
80
-    }
81
-
82
-    /**
83
-     * @inheritDoc
84
-     */
85
-    public function getStream(): StreamInterface
86
-    {
87
-        if ($this->isMoved) {
88
-            throw new RuntimeException('Uploaded file was moved');
89
-        }
90
-
91
-        if ($this->errorCode !== UPLOAD_ERR_OK) {
92
-            throw new RuntimeException($this->errorMessage, $this->errorCode);
93
-        }
94
-
95
-        if ($this->stream === null) {
96
-            throw new RuntimeException('Uploaded file has no stream');
97
-        }
98
-
99
-        return $this->stream;
100
-    }
101
-
102
-    /**
103
-     * @inheritDoc
104
-     */
105
-    public function moveTo($targetPath): void
106
-    {
107
-        /** @psalm-suppress TypeDoesNotContainType */
108
-        if (!is_string($targetPath)) {
109
-            throw new TypeError(sprintf(
110
-                'Argument #1 ($targetPath) must be of type string, %s given',
111
-                gettype($targetPath),
112
-            ));
113
-        }
114
-
115
-        $sourceStream = $this->getStream();
116
-
117
-        $sourcePath = $sourceStream->getMetadata('uri');
118
-        if (!is_string($sourcePath) || !is_file($sourcePath) || !is_readable($sourcePath)) {
119
-            throw new RuntimeException('Uploaded file does not exist or is not readable');
120
-        }
121
-
122
-        $sourceDirname = dirname($sourcePath);
123
-        if (!is_writable($sourceDirname)) {
124
-            throw new RuntimeException('To move the uploaded file, the source directory must be writable');
125
-        }
126
-
127
-        $targetDirname = dirname($targetPath);
128
-        if (!is_dir($targetDirname) || !is_writable($targetDirname)) {
129
-            throw new RuntimeException('To move the uploaded file, the target directory must exist and be writable');
130
-        }
131
-
132
-        try {
133
-            $this->isMoved = is_uploaded_file($sourcePath)
134
-                ? move_uploaded_file($sourcePath, $targetPath)
135
-                : rename($sourcePath, $targetPath);
136
-        } catch (Throwable $e) {
137
-        }
138
-
139
-        if (!$this->isMoved) {
140
-            throw new RuntimeException('Failed to move the uploaded file');
141
-        }
142
-
143
-        $sourceStream->close();
144
-        $this->stream = null;
145
-    }
146
-
147
-    /**
148
-     * @inheritDoc
149
-     */
150
-    public function getSize(): ?int
151
-    {
152
-        return $this->size;
153
-    }
154
-
155
-    /**
156
-     * @inheritDoc
157
-     */
158
-    public function getError(): int
159
-    {
160
-        return $this->errorCode;
161
-    }
162
-
163
-    /**
164
-     * @inheritDoc
165
-     */
166
-    public function getClientFilename(): ?string
167
-    {
168
-        return $this->clientFilename;
169
-    }
170
-
171
-    /**
172
-     * @inheritDoc
173
-     */
174
-    public function getClientMediaType(): ?string
175
-    {
176
-        return $this->clientMediaType;
177
-    }
43
+	/**
44
+	 * @link https://www.php.net/manual/en/features.file-upload.errors.php
45
+	 *
46
+	 * @var array<int, non-empty-string>
47
+	 */
48
+	public const UPLOAD_ERRORS = [
49
+		UPLOAD_ERR_OK         => 'No error',
50
+		UPLOAD_ERR_INI_SIZE   => 'Uploaded file exceeds the upload_max_filesize directive in the php.ini',
51
+		UPLOAD_ERR_FORM_SIZE  => 'Uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form',
52
+		UPLOAD_ERR_PARTIAL    => 'Uploaded file was only partially uploaded',
53
+		UPLOAD_ERR_NO_FILE    => 'No file was uploaded',
54
+		UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary directory',
55
+		UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
56
+		UPLOAD_ERR_EXTENSION  => 'File upload was stopped by a PHP extension',
57
+	];
58
+
59
+	private ?StreamInterface $stream;
60
+	private ?int $size;
61
+	private int $errorCode;
62
+	private string $errorMessage;
63
+	private ?string $clientFilename;
64
+	private ?string $clientMediaType;
65
+	private bool $isMoved = false;
66
+
67
+	public function __construct(
68
+		?StreamInterface $stream,
69
+		?int $size = null,
70
+		int $error = UPLOAD_ERR_OK,
71
+		?string $clientFilename = null,
72
+		?string $clientMediaType = null
73
+	) {
74
+		$this->stream = $stream;
75
+		$this->size = $size;
76
+		$this->errorCode = $error;
77
+		$this->errorMessage = self::UPLOAD_ERRORS[$error] ?? 'Unknown file upload error';
78
+		$this->clientFilename = $clientFilename;
79
+		$this->clientMediaType = $clientMediaType;
80
+	}
81
+
82
+	/**
83
+	 * @inheritDoc
84
+	 */
85
+	public function getStream(): StreamInterface
86
+	{
87
+		if ($this->isMoved) {
88
+			throw new RuntimeException('Uploaded file was moved');
89
+		}
90
+
91
+		if ($this->errorCode !== UPLOAD_ERR_OK) {
92
+			throw new RuntimeException($this->errorMessage, $this->errorCode);
93
+		}
94
+
95
+		if ($this->stream === null) {
96
+			throw new RuntimeException('Uploaded file has no stream');
97
+		}
98
+
99
+		return $this->stream;
100
+	}
101
+
102
+	/**
103
+	 * @inheritDoc
104
+	 */
105
+	public function moveTo($targetPath): void
106
+	{
107
+		/** @psalm-suppress TypeDoesNotContainType */
108
+		if (!is_string($targetPath)) {
109
+			throw new TypeError(sprintf(
110
+				'Argument #1 ($targetPath) must be of type string, %s given',
111
+				gettype($targetPath),
112
+			));
113
+		}
114
+
115
+		$sourceStream = $this->getStream();
116
+
117
+		$sourcePath = $sourceStream->getMetadata('uri');
118
+		if (!is_string($sourcePath) || !is_file($sourcePath) || !is_readable($sourcePath)) {
119
+			throw new RuntimeException('Uploaded file does not exist or is not readable');
120
+		}
121
+
122
+		$sourceDirname = dirname($sourcePath);
123
+		if (!is_writable($sourceDirname)) {
124
+			throw new RuntimeException('To move the uploaded file, the source directory must be writable');
125
+		}
126
+
127
+		$targetDirname = dirname($targetPath);
128
+		if (!is_dir($targetDirname) || !is_writable($targetDirname)) {
129
+			throw new RuntimeException('To move the uploaded file, the target directory must exist and be writable');
130
+		}
131
+
132
+		try {
133
+			$this->isMoved = is_uploaded_file($sourcePath)
134
+				? move_uploaded_file($sourcePath, $targetPath)
135
+				: rename($sourcePath, $targetPath);
136
+		} catch (Throwable $e) {
137
+		}
138
+
139
+		if (!$this->isMoved) {
140
+			throw new RuntimeException('Failed to move the uploaded file');
141
+		}
142
+
143
+		$sourceStream->close();
144
+		$this->stream = null;
145
+	}
146
+
147
+	/**
148
+	 * @inheritDoc
149
+	 */
150
+	public function getSize(): ?int
151
+	{
152
+		return $this->size;
153
+	}
154
+
155
+	/**
156
+	 * @inheritDoc
157
+	 */
158
+	public function getError(): int
159
+	{
160
+		return $this->errorCode;
161
+	}
162
+
163
+	/**
164
+	 * @inheritDoc
165
+	 */
166
+	public function getClientFilename(): ?string
167
+	{
168
+		return $this->clientFilename;
169
+	}
170
+
171
+	/**
172
+	 * @inheritDoc
173
+	 */
174
+	public function getClientMediaType(): ?string
175
+	{
176
+		return $this->clientMediaType;
177
+	}
178 178
 }
Please login to merge, or discard this patch.
src/UploadedFileFactory.php 1 patch
Indentation   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -19,22 +19,22 @@
 block discarded – undo
19 19
 
20 20
 class UploadedFileFactory implements UploadedFileFactoryInterface
21 21
 {
22
-    /**
23
-     * @inheritDoc
24
-     */
25
-    public function createUploadedFile(
26
-        StreamInterface $stream,
27
-        ?int $size = null,
28
-        int $error = UPLOAD_ERR_OK,
29
-        ?string $clientFilename = null,
30
-        ?string $clientMediaType = null
31
-    ): UploadedFileInterface {
32
-        return new UploadedFile(
33
-            $stream,
34
-            $size,
35
-            $error,
36
-            $clientFilename,
37
-            $clientMediaType
38
-        );
39
-    }
22
+	/**
23
+	 * @inheritDoc
24
+	 */
25
+	public function createUploadedFile(
26
+		StreamInterface $stream,
27
+		?int $size = null,
28
+		int $error = UPLOAD_ERR_OK,
29
+		?string $clientFilename = null,
30
+		?string $clientMediaType = null
31
+	): UploadedFileInterface {
32
+		return new UploadedFile(
33
+			$stream,
34
+			$size,
35
+			$error,
36
+			$clientFilename,
37
+			$clientMediaType
38
+		);
39
+	}
40 40
 }
Please login to merge, or discard this patch.
src/RequestFactory.php 1 patch
Indentation   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -16,11 +16,11 @@
 block discarded – undo
16 16
 
17 17
 class RequestFactory implements RequestFactoryInterface
18 18
 {
19
-    /**
20
-     * @inheritDoc
21
-     */
22
-    public function createRequest(string $method, $uri): RequestInterface
23
-    {
24
-        return new Request($method, $uri);
25
-    }
19
+	/**
20
+	 * @inheritDoc
21
+	 */
22
+	public function createRequest(string $method, $uri): RequestInterface
23
+	{
24
+		return new Request($method, $uri);
25
+	}
26 26
 }
Please login to merge, or discard this patch.
src/ResponseFactory.php 1 patch
Indentation   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -16,11 +16,11 @@
 block discarded – undo
16 16
 
17 17
 class ResponseFactory implements ResponseFactoryInterface
18 18
 {
19
-    /**
20
-     * @inheritDoc
21
-     */
22
-    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
23
-    {
24
-        return new Response($code, $reasonPhrase);
25
-    }
19
+	/**
20
+	 * @inheritDoc
21
+	 */
22
+	public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
23
+	{
24
+		return new Response($code, $reasonPhrase);
25
+	}
26 26
 }
Please login to merge, or discard this patch.
src/Stream.php 1 patch
Indentation   +288 added lines, -288 removed lines patch added patch discarded remove patch
@@ -32,292 +32,292 @@
 block discarded – undo
32 32
 
33 33
 class Stream implements StreamInterface
34 34
 {
35
-    /**
36
-     * @var resource|null
37
-     */
38
-    private $resource;
39
-
40
-    private bool $autoClose;
41
-
42
-    /**
43
-     * @param mixed $resource
44
-     *
45
-     * @throws InvalidArgumentException
46
-     */
47
-    public function __construct($resource, bool $autoClose = true)
48
-    {
49
-        if (!is_resource($resource)) {
50
-            throw new InvalidArgumentException('Unexpected stream resource');
51
-        }
52
-
53
-        $this->resource = $resource;
54
-        $this->autoClose = $autoClose;
55
-    }
56
-
57
-    /**
58
-     * @param mixed $resource
59
-     *
60
-     * @throws InvalidArgumentException
61
-     */
62
-    public static function create($resource): StreamInterface
63
-    {
64
-        if ($resource instanceof StreamInterface) {
65
-            return $resource;
66
-        }
67
-
68
-        return new self($resource);
69
-    }
70
-
71
-    public function __destruct()
72
-    {
73
-        if ($this->autoClose) {
74
-            $this->close();
75
-        }
76
-    }
77
-
78
-    /**
79
-     * @inheritDoc
80
-     */
81
-    public function detach()
82
-    {
83
-        $resource = $this->resource;
84
-        $this->resource = null;
85
-
86
-        return $resource;
87
-    }
88
-
89
-    /**
90
-     * @inheritDoc
91
-     */
92
-    public function close(): void
93
-    {
94
-        $resource = $this->detach();
95
-        if (!is_resource($resource)) {
96
-            return;
97
-        }
98
-
99
-        fclose($resource);
100
-    }
101
-
102
-    /**
103
-     * @inheritDoc
104
-     */
105
-    public function eof(): bool
106
-    {
107
-        if (!is_resource($this->resource)) {
108
-            return true;
109
-        }
110
-
111
-        return feof($this->resource);
112
-    }
113
-
114
-    /**
115
-     * @inheritDoc
116
-     */
117
-    public function tell(): int
118
-    {
119
-        if (!is_resource($this->resource)) {
120
-            throw new RuntimeException('Stream has no resource');
121
-        }
122
-
123
-        $result = ftell($this->resource);
124
-        if ($result === false) {
125
-            throw new RuntimeException('Unable to get the stream pointer position');
126
-        }
127
-
128
-        return $result;
129
-    }
130
-
131
-    /**
132
-     * @inheritDoc
133
-     */
134
-    public function isSeekable(): bool
135
-    {
136
-        if (!is_resource($this->resource)) {
137
-            return false;
138
-        }
139
-
140
-        /** @var array{seekable: bool} */
141
-        $metadata = stream_get_meta_data($this->resource);
142
-
143
-        return $metadata['seekable'];
144
-    }
145
-
146
-    /**
147
-     * @inheritDoc
148
-     */
149
-    public function rewind(): void
150
-    {
151
-        $this->seek(0);
152
-    }
153
-
154
-    /**
155
-     * @inheritDoc
156
-     */
157
-    public function seek($offset, $whence = SEEK_SET): void
158
-    {
159
-        if (!is_resource($this->resource)) {
160
-            throw new RuntimeException('Stream has no resource');
161
-        }
162
-
163
-        if (!$this->isSeekable()) {
164
-            throw new RuntimeException('Stream is not seekable');
165
-        }
166
-
167
-        $result = fseek($this->resource, $offset, $whence);
168
-        if ($result !== 0) {
169
-            throw new RuntimeException('Unable to move the stream pointer position');
170
-        }
171
-    }
172
-
173
-    /**
174
-     * @inheritDoc
175
-     */
176
-    public function isWritable(): bool
177
-    {
178
-        if (!is_resource($this->resource)) {
179
-            return false;
180
-        }
181
-
182
-        /** @var array{mode: string} */
183
-        $metadata = stream_get_meta_data($this->resource);
184
-
185
-        return strpbrk($metadata['mode'], '+acwx') !== false;
186
-    }
187
-
188
-    /**
189
-     * @inheritDoc
190
-     */
191
-    public function write($string): int
192
-    {
193
-        if (!is_resource($this->resource)) {
194
-            throw new RuntimeException('Stream has no resource');
195
-        }
196
-
197
-        if (!$this->isWritable()) {
198
-            throw new RuntimeException('Stream is not writable');
199
-        }
200
-
201
-        $result = fwrite($this->resource, $string);
202
-        if ($result === false) {
203
-            throw new RuntimeException('Unable to write to the stream');
204
-        }
205
-
206
-        return $result;
207
-    }
208
-
209
-    /**
210
-     * @inheritDoc
211
-     */
212
-    public function isReadable(): bool
213
-    {
214
-        if (!is_resource($this->resource)) {
215
-            return false;
216
-        }
217
-
218
-        /** @var array{mode: string} */
219
-        $metadata = stream_get_meta_data($this->resource);
220
-
221
-        return strpbrk($metadata['mode'], '+r') !== false;
222
-    }
223
-
224
-    /**
225
-     * @inheritDoc
226
-     *
227
-     * @psalm-param int $length
228
-     * @phpstan-param int<1, max> $length
229
-     */
230
-    public function read($length): string
231
-    {
232
-        if (!is_resource($this->resource)) {
233
-            throw new RuntimeException('Stream has no resource');
234
-        }
235
-
236
-        if (!$this->isReadable()) {
237
-            throw new RuntimeException('Stream is not readable');
238
-        }
239
-
240
-        $result = fread($this->resource, $length);
241
-        if ($result === false) {
242
-            throw new RuntimeException('Unable to read from the stream');
243
-        }
244
-
245
-        return $result;
246
-    }
247
-
248
-    /**
249
-     * @inheritDoc
250
-     */
251
-    public function getContents(): string
252
-    {
253
-        if (!is_resource($this->resource)) {
254
-            throw new RuntimeException('Stream has no resource');
255
-        }
256
-
257
-        if (!$this->isReadable()) {
258
-            throw new RuntimeException('Stream is not readable');
259
-        }
260
-
261
-        $result = stream_get_contents($this->resource);
262
-        if ($result === false) {
263
-            throw new RuntimeException('Unable to read the remainder of the stream');
264
-        }
265
-
266
-        return $result;
267
-    }
268
-
269
-    /**
270
-     * @inheritDoc
271
-     */
272
-    public function getMetadata($key = null)
273
-    {
274
-        if (!is_resource($this->resource)) {
275
-            return null;
276
-        }
277
-
278
-        $metadata = stream_get_meta_data($this->resource);
279
-        if ($key === null) {
280
-            return $metadata;
281
-        }
282
-
283
-        return $metadata[$key] ?? null;
284
-    }
285
-
286
-    /**
287
-     * @inheritDoc
288
-     */
289
-    public function getSize(): ?int
290
-    {
291
-        if (!is_resource($this->resource)) {
292
-            return null;
293
-        }
294
-
295
-        /** @var array{size: int}|false */
296
-        $stats = fstat($this->resource);
297
-        if ($stats === false) {
298
-            return null;
299
-        }
300
-
301
-        return $stats['size'];
302
-    }
303
-
304
-    /**
305
-     * @inheritDoc
306
-     */
307
-    public function __toString(): string
308
-    {
309
-        if (!$this->isReadable()) {
310
-            return '';
311
-        }
312
-
313
-        try {
314
-            if ($this->isSeekable()) {
315
-                $this->rewind();
316
-            }
317
-
318
-            return $this->getContents();
319
-        } catch (Throwable $e) {
320
-            return '';
321
-        }
322
-    }
35
+	/**
36
+	 * @var resource|null
37
+	 */
38
+	private $resource;
39
+
40
+	private bool $autoClose;
41
+
42
+	/**
43
+	 * @param mixed $resource
44
+	 *
45
+	 * @throws InvalidArgumentException
46
+	 */
47
+	public function __construct($resource, bool $autoClose = true)
48
+	{
49
+		if (!is_resource($resource)) {
50
+			throw new InvalidArgumentException('Unexpected stream resource');
51
+		}
52
+
53
+		$this->resource = $resource;
54
+		$this->autoClose = $autoClose;
55
+	}
56
+
57
+	/**
58
+	 * @param mixed $resource
59
+	 *
60
+	 * @throws InvalidArgumentException
61
+	 */
62
+	public static function create($resource): StreamInterface
63
+	{
64
+		if ($resource instanceof StreamInterface) {
65
+			return $resource;
66
+		}
67
+
68
+		return new self($resource);
69
+	}
70
+
71
+	public function __destruct()
72
+	{
73
+		if ($this->autoClose) {
74
+			$this->close();
75
+		}
76
+	}
77
+
78
+	/**
79
+	 * @inheritDoc
80
+	 */
81
+	public function detach()
82
+	{
83
+		$resource = $this->resource;
84
+		$this->resource = null;
85
+
86
+		return $resource;
87
+	}
88
+
89
+	/**
90
+	 * @inheritDoc
91
+	 */
92
+	public function close(): void
93
+	{
94
+		$resource = $this->detach();
95
+		if (!is_resource($resource)) {
96
+			return;
97
+		}
98
+
99
+		fclose($resource);
100
+	}
101
+
102
+	/**
103
+	 * @inheritDoc
104
+	 */
105
+	public function eof(): bool
106
+	{
107
+		if (!is_resource($this->resource)) {
108
+			return true;
109
+		}
110
+
111
+		return feof($this->resource);
112
+	}
113
+
114
+	/**
115
+	 * @inheritDoc
116
+	 */
117
+	public function tell(): int
118
+	{
119
+		if (!is_resource($this->resource)) {
120
+			throw new RuntimeException('Stream has no resource');
121
+		}
122
+
123
+		$result = ftell($this->resource);
124
+		if ($result === false) {
125
+			throw new RuntimeException('Unable to get the stream pointer position');
126
+		}
127
+
128
+		return $result;
129
+	}
130
+
131
+	/**
132
+	 * @inheritDoc
133
+	 */
134
+	public function isSeekable(): bool
135
+	{
136
+		if (!is_resource($this->resource)) {
137
+			return false;
138
+		}
139
+
140
+		/** @var array{seekable: bool} */
141
+		$metadata = stream_get_meta_data($this->resource);
142
+
143
+		return $metadata['seekable'];
144
+	}
145
+
146
+	/**
147
+	 * @inheritDoc
148
+	 */
149
+	public function rewind(): void
150
+	{
151
+		$this->seek(0);
152
+	}
153
+
154
+	/**
155
+	 * @inheritDoc
156
+	 */
157
+	public function seek($offset, $whence = SEEK_SET): void
158
+	{
159
+		if (!is_resource($this->resource)) {
160
+			throw new RuntimeException('Stream has no resource');
161
+		}
162
+
163
+		if (!$this->isSeekable()) {
164
+			throw new RuntimeException('Stream is not seekable');
165
+		}
166
+
167
+		$result = fseek($this->resource, $offset, $whence);
168
+		if ($result !== 0) {
169
+			throw new RuntimeException('Unable to move the stream pointer position');
170
+		}
171
+	}
172
+
173
+	/**
174
+	 * @inheritDoc
175
+	 */
176
+	public function isWritable(): bool
177
+	{
178
+		if (!is_resource($this->resource)) {
179
+			return false;
180
+		}
181
+
182
+		/** @var array{mode: string} */
183
+		$metadata = stream_get_meta_data($this->resource);
184
+
185
+		return strpbrk($metadata['mode'], '+acwx') !== false;
186
+	}
187
+
188
+	/**
189
+	 * @inheritDoc
190
+	 */
191
+	public function write($string): int
192
+	{
193
+		if (!is_resource($this->resource)) {
194
+			throw new RuntimeException('Stream has no resource');
195
+		}
196
+
197
+		if (!$this->isWritable()) {
198
+			throw new RuntimeException('Stream is not writable');
199
+		}
200
+
201
+		$result = fwrite($this->resource, $string);
202
+		if ($result === false) {
203
+			throw new RuntimeException('Unable to write to the stream');
204
+		}
205
+
206
+		return $result;
207
+	}
208
+
209
+	/**
210
+	 * @inheritDoc
211
+	 */
212
+	public function isReadable(): bool
213
+	{
214
+		if (!is_resource($this->resource)) {
215
+			return false;
216
+		}
217
+
218
+		/** @var array{mode: string} */
219
+		$metadata = stream_get_meta_data($this->resource);
220
+
221
+		return strpbrk($metadata['mode'], '+r') !== false;
222
+	}
223
+
224
+	/**
225
+	 * @inheritDoc
226
+	 *
227
+	 * @psalm-param int $length
228
+	 * @phpstan-param int<1, max> $length
229
+	 */
230
+	public function read($length): string
231
+	{
232
+		if (!is_resource($this->resource)) {
233
+			throw new RuntimeException('Stream has no resource');
234
+		}
235
+
236
+		if (!$this->isReadable()) {
237
+			throw new RuntimeException('Stream is not readable');
238
+		}
239
+
240
+		$result = fread($this->resource, $length);
241
+		if ($result === false) {
242
+			throw new RuntimeException('Unable to read from the stream');
243
+		}
244
+
245
+		return $result;
246
+	}
247
+
248
+	/**
249
+	 * @inheritDoc
250
+	 */
251
+	public function getContents(): string
252
+	{
253
+		if (!is_resource($this->resource)) {
254
+			throw new RuntimeException('Stream has no resource');
255
+		}
256
+
257
+		if (!$this->isReadable()) {
258
+			throw new RuntimeException('Stream is not readable');
259
+		}
260
+
261
+		$result = stream_get_contents($this->resource);
262
+		if ($result === false) {
263
+			throw new RuntimeException('Unable to read the remainder of the stream');
264
+		}
265
+
266
+		return $result;
267
+	}
268
+
269
+	/**
270
+	 * @inheritDoc
271
+	 */
272
+	public function getMetadata($key = null)
273
+	{
274
+		if (!is_resource($this->resource)) {
275
+			return null;
276
+		}
277
+
278
+		$metadata = stream_get_meta_data($this->resource);
279
+		if ($key === null) {
280
+			return $metadata;
281
+		}
282
+
283
+		return $metadata[$key] ?? null;
284
+	}
285
+
286
+	/**
287
+	 * @inheritDoc
288
+	 */
289
+	public function getSize(): ?int
290
+	{
291
+		if (!is_resource($this->resource)) {
292
+			return null;
293
+		}
294
+
295
+		/** @var array{size: int}|false */
296
+		$stats = fstat($this->resource);
297
+		if ($stats === false) {
298
+			return null;
299
+		}
300
+
301
+		return $stats['size'];
302
+	}
303
+
304
+	/**
305
+	 * @inheritDoc
306
+	 */
307
+	public function __toString(): string
308
+	{
309
+		if (!$this->isReadable()) {
310
+			return '';
311
+		}
312
+
313
+		try {
314
+			if ($this->isSeekable()) {
315
+				$this->rewind();
316
+			}
317
+
318
+			return $this->getContents();
319
+		} catch (Throwable $e) {
320
+			return '';
321
+		}
322
+	}
323 323
 }
Please login to merge, or discard this patch.
src/Response/HtmlResponse.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -22,42 +22,42 @@
 block discarded – undo
22 22
 
23 23
 final class HtmlResponse extends Response
24 24
 {
25
-    /**
26
-     * @param mixed $html
27
-     *
28
-     * @throws InvalidArgumentException
29
-     */
30
-    public function __construct(int $statusCode, $html)
31
-    {
32
-        parent::__construct($statusCode);
33
-
34
-        $this->setBody(self::createBody($html));
35
-        $this->setHeader('Content-Type', 'text/html; charset=utf-8');
36
-    }
37
-
38
-    /**
39
-     * @param mixed $html
40
-     *
41
-     * @throws InvalidArgumentException
42
-     */
43
-    private static function createBody($html): StreamInterface
44
-    {
45
-        if ($html instanceof StreamInterface) {
46
-            return $html;
47
-        }
48
-
49
-        if (is_object($html) && method_exists($html, '__toString')) {
50
-            $html = (string) $html;
51
-        }
52
-
53
-        if (!is_string($html)) {
54
-            throw new InvalidArgumentException('Unable to create the HTML response due to a unexpected HTML type');
55
-        }
56
-
57
-        $stream = new PhpTempStream('r+b');
58
-        $stream->write($html);
59
-        $stream->rewind();
60
-
61
-        return $stream;
62
-    }
25
+	/**
26
+	 * @param mixed $html
27
+	 *
28
+	 * @throws InvalidArgumentException
29
+	 */
30
+	public function __construct(int $statusCode, $html)
31
+	{
32
+		parent::__construct($statusCode);
33
+
34
+		$this->setBody(self::createBody($html));
35
+		$this->setHeader('Content-Type', 'text/html; charset=utf-8');
36
+	}
37
+
38
+	/**
39
+	 * @param mixed $html
40
+	 *
41
+	 * @throws InvalidArgumentException
42
+	 */
43
+	private static function createBody($html): StreamInterface
44
+	{
45
+		if ($html instanceof StreamInterface) {
46
+			return $html;
47
+		}
48
+
49
+		if (is_object($html) && method_exists($html, '__toString')) {
50
+			$html = (string) $html;
51
+		}
52
+
53
+		if (!is_string($html)) {
54
+			throw new InvalidArgumentException('Unable to create the HTML response due to a unexpected HTML type');
55
+		}
56
+
57
+		$stream = new PhpTempStream('r+b');
58
+		$stream->write($html);
59
+		$stream->rewind();
60
+
61
+		return $stream;
62
+	}
63 63
 }
Please login to merge, or discard this patch.
src/Response/JsonResponse.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -24,47 +24,47 @@
 block discarded – undo
24 24
 
25 25
 final class JsonResponse extends Response
26 26
 {
27
-    /**
28
-     * @param mixed $data
29
-     * @param int<1, max> $depth
30
-     * @psalm-param int<1, 2147483647> $depth
31
-     *
32
-     * @throws InvalidArgumentException
33
-     */
34
-    public function __construct(int $statusCode, $data, int $flags = 0, int $depth = 512)
35
-    {
36
-        parent::__construct($statusCode);
27
+	/**
28
+	 * @param mixed $data
29
+	 * @param int<1, max> $depth
30
+	 * @psalm-param int<1, 2147483647> $depth
31
+	 *
32
+	 * @throws InvalidArgumentException
33
+	 */
34
+	public function __construct(int $statusCode, $data, int $flags = 0, int $depth = 512)
35
+	{
36
+		parent::__construct($statusCode);
37 37
 
38
-        $this->setBody(self::createBody($data, $flags, $depth));
39
-        $this->setHeader('Content-Type', 'application/json; charset=utf-8');
40
-    }
38
+		$this->setBody(self::createBody($data, $flags, $depth));
39
+		$this->setHeader('Content-Type', 'application/json; charset=utf-8');
40
+	}
41 41
 
42
-    /**
43
-     * @param mixed $data
44
-     * @param int<1, max> $depth
45
-     * @psalm-param int<1, 2147483647> $depth
46
-     *
47
-     * @throws InvalidArgumentException
48
-     */
49
-    private static function createBody($data, int $flags, int $depth): StreamInterface
50
-    {
51
-        if ($data instanceof StreamInterface) {
52
-            return $data;
53
-        }
42
+	/**
43
+	 * @param mixed $data
44
+	 * @param int<1, max> $depth
45
+	 * @psalm-param int<1, 2147483647> $depth
46
+	 *
47
+	 * @throws InvalidArgumentException
48
+	 */
49
+	private static function createBody($data, int $flags, int $depth): StreamInterface
50
+	{
51
+		if ($data instanceof StreamInterface) {
52
+			return $data;
53
+		}
54 54
 
55
-        try {
56
-            $json = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth);
57
-        } catch (JsonException $e) {
58
-            throw new InvalidArgumentException(sprintf(
59
-                'Unable to create the JSON response due to an invalid data: %s',
60
-                $e->getMessage()
61
-            ), 0, $e);
62
-        }
55
+		try {
56
+			$json = json_encode($data, $flags | JSON_THROW_ON_ERROR, $depth);
57
+		} catch (JsonException $e) {
58
+			throw new InvalidArgumentException(sprintf(
59
+				'Unable to create the JSON response due to an invalid data: %s',
60
+				$e->getMessage()
61
+			), 0, $e);
62
+		}
63 63
 
64
-        $stream = new PhpTempStream('r+b');
65
-        $stream->write($json);
66
-        $stream->rewind();
64
+		$stream = new PhpTempStream('r+b');
65
+		$stream->write($json);
66
+		$stream->rewind();
67 67
 
68
-        return $stream;
69
-    }
68
+		return $stream;
69
+	}
70 70
 }
Please login to merge, or discard this patch.
src/Request/UrlEncodedRequest.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -31,51 +31,51 @@
 block discarded – undo
31 31
  */
32 32
 final class UrlEncodedRequest extends Request
33 33
 {
34
-    public const ENCODING_TYPE_RFC1738 = PHP_QUERY_RFC1738;
35
-    public const ENCODING_TYPE_RFC3986 = PHP_QUERY_RFC3986;
34
+	public const ENCODING_TYPE_RFC1738 = PHP_QUERY_RFC1738;
35
+	public const ENCODING_TYPE_RFC3986 = PHP_QUERY_RFC3986;
36 36
 
37
-    /**
38
-     * @param mixed $uri
39
-     * @param array<array-key, mixed>|object $data
40
-     * @param self::ENCODING_TYPE_* $encodingType
41
-     *
42
-     * @throws InvalidArgumentException
43
-     */
44
-    public function __construct(string $method, $uri, $data, int $encodingType = self::ENCODING_TYPE_RFC1738)
45
-    {
46
-        /**
47
-         * @psalm-suppress DocblockTypeContradiction
48
-         * @phpstan-ignore-next-line
49
-         */
50
-        if (!is_array($data) && !is_object($data)) {
51
-            throw new TypeError(sprintf(
52
-                'Argument #3 ($data) must be of type string, %s given',
53
-                gettype($data),
54
-            ));
55
-        }
37
+	/**
38
+	 * @param mixed $uri
39
+	 * @param array<array-key, mixed>|object $data
40
+	 * @param self::ENCODING_TYPE_* $encodingType
41
+	 *
42
+	 * @throws InvalidArgumentException
43
+	 */
44
+	public function __construct(string $method, $uri, $data, int $encodingType = self::ENCODING_TYPE_RFC1738)
45
+	{
46
+		/**
47
+		 * @psalm-suppress DocblockTypeContradiction
48
+		 * @phpstan-ignore-next-line
49
+		 */
50
+		if (!is_array($data) && !is_object($data)) {
51
+			throw new TypeError(sprintf(
52
+				'Argument #3 ($data) must be of type string, %s given',
53
+				gettype($data),
54
+			));
55
+		}
56 56
 
57
-        parent::__construct($method, $uri);
57
+		parent::__construct($method, $uri);
58 58
 
59
-        $this->setBody(self::createBody($data, $encodingType));
60
-        $this->setHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
61
-    }
59
+		$this->setBody(self::createBody($data, $encodingType));
60
+		$this->setHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
61
+	}
62 62
 
63
-    /**
64
-     * @param array<array-key, mixed>|object $data
65
-     * @param self::ENCODING_TYPE_* $encodingType
66
-     */
67
-    private static function createBody($data, int $encodingType): StreamInterface
68
-    {
69
-        if ($data instanceof StreamInterface) {
70
-            return $data;
71
-        }
63
+	/**
64
+	 * @param array<array-key, mixed>|object $data
65
+	 * @param self::ENCODING_TYPE_* $encodingType
66
+	 */
67
+	private static function createBody($data, int $encodingType): StreamInterface
68
+	{
69
+		if ($data instanceof StreamInterface) {
70
+			return $data;
71
+		}
72 72
 
73
-        $encodedData = http_build_query($data, '', '', $encodingType);
73
+		$encodedData = http_build_query($data, '', '', $encodingType);
74 74
 
75
-        $stream = new PhpTempStream('r+b');
76
-        $stream->write($encodedData);
77
-        $stream->rewind();
75
+		$stream = new PhpTempStream('r+b');
76
+		$stream->write($encodedData);
77
+		$stream->rewind();
78 78
 
79
-        return $stream;
80
-    }
79
+		return $stream;
80
+	}
81 81
 }
Please login to merge, or discard this patch.
src/Message.php 1 patch
Indentation   +292 added lines, -292 removed lines patch added patch discarded remove patch
@@ -24,296 +24,296 @@
 block discarded – undo
24 24
 
25 25
 abstract class Message implements MessageInterface
26 26
 {
27
-    /**
28
-     * @deprecated 3.2.0
29
-     */
30
-    public const ALLOWED_HTTP_VERSIONS = ['1.0', '1.1', '2.0', '2'];
31
-
32
-    public const HTTP_VERSION_REGEX = '/^[0-9](?:[.][0-9])?$/';
33
-    public const DEFAULT_HTTP_VERSION = '1.1';
34
-
35
-    private string $protocolVersion = self::DEFAULT_HTTP_VERSION;
36
-
37
-    /**
38
-     * @var array<string, list<string>>
39
-     */
40
-    private array $headers = [];
41
-
42
-    /**
43
-     * @var array<string, string>
44
-     */
45
-    private array $headerNames = [];
46
-
47
-    private ?StreamInterface $body = null;
48
-
49
-    /**
50
-     * @inheritDoc
51
-     */
52
-    public function getProtocolVersion(): string
53
-    {
54
-        return $this->protocolVersion;
55
-    }
56
-
57
-    /**
58
-     * @inheritDoc
59
-     *
60
-     * @throws InvalidArgumentException
61
-     */
62
-    public function withProtocolVersion($version): MessageInterface
63
-    {
64
-        $clone = clone $this;
65
-        $clone->setProtocolVersion($version);
66
-
67
-        return $clone;
68
-    }
69
-
70
-    /**
71
-     * @inheritDoc
72
-     */
73
-    public function getHeaders(): array
74
-    {
75
-        return $this->headers;
76
-    }
77
-
78
-    /**
79
-     * @inheritDoc
80
-     */
81
-    public function hasHeader($name): bool
82
-    {
83
-        $key = strtolower($name);
84
-
85
-        return isset($this->headerNames[$key]);
86
-    }
87
-
88
-    /**
89
-     * @inheritDoc
90
-     */
91
-    public function getHeader($name): array
92
-    {
93
-        $key = strtolower($name);
94
-
95
-        if (!isset($this->headerNames[$key])) {
96
-            return [];
97
-        }
98
-
99
-        return $this->headers[$this->headerNames[$key]];
100
-    }
101
-
102
-    /**
103
-     * @inheritDoc
104
-     */
105
-    public function getHeaderLine($name): string
106
-    {
107
-        $key = strtolower($name);
108
-
109
-        if (!isset($this->headerNames[$key])) {
110
-            return '';
111
-        }
112
-
113
-        return implode(',', $this->headers[$this->headerNames[$key]]);
114
-    }
115
-
116
-    /**
117
-     * @inheritDoc
118
-     */
119
-    public function withHeader($name, $value): MessageInterface
120
-    {
121
-        $clone = clone $this;
122
-        $clone->setHeader($name, $value, true);
123
-
124
-        return $clone;
125
-    }
126
-
127
-    /**
128
-     * @inheritDoc
129
-     */
130
-    public function withAddedHeader($name, $value): MessageInterface
131
-    {
132
-        $clone = clone $this;
133
-        $clone->setHeader($name, $value, false);
134
-
135
-        return $clone;
136
-    }
137
-
138
-    /**
139
-     * @inheritDoc
140
-     */
141
-    public function withoutHeader($name): MessageInterface
142
-    {
143
-        $clone = clone $this;
144
-        $clone->deleteHeader($name);
145
-
146
-        return $clone;
147
-    }
148
-
149
-    /**
150
-     * @inheritDoc
151
-     */
152
-    public function getBody(): StreamInterface
153
-    {
154
-        return $this->body ??= new PhpTempStream();
155
-    }
156
-
157
-    /**
158
-     * @inheritDoc
159
-     */
160
-    public function withBody(StreamInterface $body): MessageInterface
161
-    {
162
-        $clone = clone $this;
163
-        $clone->setBody($body);
164
-
165
-        return $clone;
166
-    }
167
-
168
-    /**
169
-     * Sets the given HTTP version to the message
170
-     *
171
-     * @param string $protocolVersion
172
-     *
173
-     * @throws InvalidArgumentException
174
-     */
175
-    final protected function setProtocolVersion($protocolVersion): void
176
-    {
177
-        $this->validateProtocolVersion($protocolVersion);
178
-
179
-        $this->protocolVersion = $protocolVersion;
180
-    }
181
-
182
-    /**
183
-     * Sets a new header to the message with the given name and value(s)
184
-     *
185
-     * @param string $name
186
-     * @param string|string[] $value
187
-     *
188
-     * @throws InvalidArgumentException
189
-     */
190
-    final protected function setHeader($name, $value, bool $replace = true): void
191
-    {
192
-        if (!is_array($value)) {
193
-            $value = [$value];
194
-        }
195
-
196
-        $this->validateHeaderName($name);
197
-        $this->validateHeaderValue($name, $value);
198
-
199
-        $replace and $this->deleteHeader($name);
200
-
201
-        $key = strtolower($name);
202
-
203
-        $this->headerNames[$key] ??= $name;
204
-        $this->headers[$this->headerNames[$key]] ??= [];
205
-
206
-        foreach ($value as $item) {
207
-            $this->headers[$this->headerNames[$key]][] = $item;
208
-        }
209
-    }
210
-
211
-    /**
212
-     * Sets the given headers to the message
213
-     *
214
-     * @param array<string, string|string[]> $headers
215
-     *
216
-     * @throws InvalidArgumentException
217
-     */
218
-    final protected function setHeaders(array $headers): void
219
-    {
220
-        foreach ($headers as $name => $value) {
221
-            $this->setHeader($name, $value, false);
222
-        }
223
-    }
224
-
225
-    /**
226
-     * Deletes a header from the message by the given name
227
-     *
228
-     * @param string $name
229
-     */
230
-    final protected function deleteHeader($name): void
231
-    {
232
-        $key = strtolower($name);
233
-
234
-        if (isset($this->headerNames[$key])) {
235
-            unset($this->headers[$this->headerNames[$key]]);
236
-            unset($this->headerNames[$key]);
237
-        }
238
-    }
239
-
240
-    /**
241
-     * Sets the given body to the message
242
-     */
243
-    final protected function setBody(StreamInterface $body): void
244
-    {
245
-        $this->body = $body;
246
-    }
247
-
248
-    /**
249
-     * Validates the given HTTP version
250
-     *
251
-     * @param mixed $protocolVersion
252
-     *
253
-     * @throws InvalidArgumentException
254
-     */
255
-    private function validateProtocolVersion($protocolVersion): void
256
-    {
257
-        if ($protocolVersion === '') {
258
-            throw new InvalidArgumentException('HTTP version cannot be an empty');
259
-        }
260
-
261
-        if (!is_string($protocolVersion)) {
262
-            throw new InvalidArgumentException('HTTP version must be a string');
263
-        }
264
-
265
-        if (!preg_match(self::HTTP_VERSION_REGEX, $protocolVersion)) {
266
-            throw new InvalidArgumentException('HTTP version is invalid');
267
-        }
268
-    }
269
-
270
-    /**
271
-     * Validates the given header name
272
-     *
273
-     * @param mixed $name
274
-     *
275
-     * @throws InvalidArgumentException
276
-     */
277
-    private function validateHeaderName($name): void
278
-    {
279
-        if ($name === '') {
280
-            throw new InvalidArgumentException('HTTP header name cannot be an empty');
281
-        }
282
-
283
-        if (!is_string($name)) {
284
-            throw new InvalidArgumentException('HTTP header name must be a string');
285
-        }
286
-
287
-        if (!preg_match(HeaderInterface::RFC7230_TOKEN_REGEX, $name)) {
288
-            throw new InvalidArgumentException('HTTP header name is invalid');
289
-        }
290
-    }
291
-
292
-    /**
293
-     * Validates the given header value
294
-     *
295
-     * @param array<array-key, mixed> $value
296
-     *
297
-     * @throws InvalidArgumentException
298
-     */
299
-    private function validateHeaderValue(string $name, array $value): void
300
-    {
301
-        if ($value === []) {
302
-            throw new InvalidArgumentException("The value of the HTTP header $name cannot be an empty array");
303
-        }
304
-
305
-        foreach ($value as $key => $item) {
306
-            if ($item === '') {
307
-                continue;
308
-            }
309
-
310
-            if (!is_string($item)) {
311
-                throw new InvalidArgumentException("The value of the HTTP header $name:$key must be a string");
312
-            }
313
-
314
-            if (!preg_match(HeaderInterface::RFC7230_FIELD_VALUE_REGEX, $item)) {
315
-                throw new InvalidArgumentException("The value of the HTTP header $name:$key is invalid");
316
-            }
317
-        }
318
-    }
27
+	/**
28
+	 * @deprecated 3.2.0
29
+	 */
30
+	public const ALLOWED_HTTP_VERSIONS = ['1.0', '1.1', '2.0', '2'];
31
+
32
+	public const HTTP_VERSION_REGEX = '/^[0-9](?:[.][0-9])?$/';
33
+	public const DEFAULT_HTTP_VERSION = '1.1';
34
+
35
+	private string $protocolVersion = self::DEFAULT_HTTP_VERSION;
36
+
37
+	/**
38
+	 * @var array<string, list<string>>
39
+	 */
40
+	private array $headers = [];
41
+
42
+	/**
43
+	 * @var array<string, string>
44
+	 */
45
+	private array $headerNames = [];
46
+
47
+	private ?StreamInterface $body = null;
48
+
49
+	/**
50
+	 * @inheritDoc
51
+	 */
52
+	public function getProtocolVersion(): string
53
+	{
54
+		return $this->protocolVersion;
55
+	}
56
+
57
+	/**
58
+	 * @inheritDoc
59
+	 *
60
+	 * @throws InvalidArgumentException
61
+	 */
62
+	public function withProtocolVersion($version): MessageInterface
63
+	{
64
+		$clone = clone $this;
65
+		$clone->setProtocolVersion($version);
66
+
67
+		return $clone;
68
+	}
69
+
70
+	/**
71
+	 * @inheritDoc
72
+	 */
73
+	public function getHeaders(): array
74
+	{
75
+		return $this->headers;
76
+	}
77
+
78
+	/**
79
+	 * @inheritDoc
80
+	 */
81
+	public function hasHeader($name): bool
82
+	{
83
+		$key = strtolower($name);
84
+
85
+		return isset($this->headerNames[$key]);
86
+	}
87
+
88
+	/**
89
+	 * @inheritDoc
90
+	 */
91
+	public function getHeader($name): array
92
+	{
93
+		$key = strtolower($name);
94
+
95
+		if (!isset($this->headerNames[$key])) {
96
+			return [];
97
+		}
98
+
99
+		return $this->headers[$this->headerNames[$key]];
100
+	}
101
+
102
+	/**
103
+	 * @inheritDoc
104
+	 */
105
+	public function getHeaderLine($name): string
106
+	{
107
+		$key = strtolower($name);
108
+
109
+		if (!isset($this->headerNames[$key])) {
110
+			return '';
111
+		}
112
+
113
+		return implode(',', $this->headers[$this->headerNames[$key]]);
114
+	}
115
+
116
+	/**
117
+	 * @inheritDoc
118
+	 */
119
+	public function withHeader($name, $value): MessageInterface
120
+	{
121
+		$clone = clone $this;
122
+		$clone->setHeader($name, $value, true);
123
+
124
+		return $clone;
125
+	}
126
+
127
+	/**
128
+	 * @inheritDoc
129
+	 */
130
+	public function withAddedHeader($name, $value): MessageInterface
131
+	{
132
+		$clone = clone $this;
133
+		$clone->setHeader($name, $value, false);
134
+
135
+		return $clone;
136
+	}
137
+
138
+	/**
139
+	 * @inheritDoc
140
+	 */
141
+	public function withoutHeader($name): MessageInterface
142
+	{
143
+		$clone = clone $this;
144
+		$clone->deleteHeader($name);
145
+
146
+		return $clone;
147
+	}
148
+
149
+	/**
150
+	 * @inheritDoc
151
+	 */
152
+	public function getBody(): StreamInterface
153
+	{
154
+		return $this->body ??= new PhpTempStream();
155
+	}
156
+
157
+	/**
158
+	 * @inheritDoc
159
+	 */
160
+	public function withBody(StreamInterface $body): MessageInterface
161
+	{
162
+		$clone = clone $this;
163
+		$clone->setBody($body);
164
+
165
+		return $clone;
166
+	}
167
+
168
+	/**
169
+	 * Sets the given HTTP version to the message
170
+	 *
171
+	 * @param string $protocolVersion
172
+	 *
173
+	 * @throws InvalidArgumentException
174
+	 */
175
+	final protected function setProtocolVersion($protocolVersion): void
176
+	{
177
+		$this->validateProtocolVersion($protocolVersion);
178
+
179
+		$this->protocolVersion = $protocolVersion;
180
+	}
181
+
182
+	/**
183
+	 * Sets a new header to the message with the given name and value(s)
184
+	 *
185
+	 * @param string $name
186
+	 * @param string|string[] $value
187
+	 *
188
+	 * @throws InvalidArgumentException
189
+	 */
190
+	final protected function setHeader($name, $value, bool $replace = true): void
191
+	{
192
+		if (!is_array($value)) {
193
+			$value = [$value];
194
+		}
195
+
196
+		$this->validateHeaderName($name);
197
+		$this->validateHeaderValue($name, $value);
198
+
199
+		$replace and $this->deleteHeader($name);
200
+
201
+		$key = strtolower($name);
202
+
203
+		$this->headerNames[$key] ??= $name;
204
+		$this->headers[$this->headerNames[$key]] ??= [];
205
+
206
+		foreach ($value as $item) {
207
+			$this->headers[$this->headerNames[$key]][] = $item;
208
+		}
209
+	}
210
+
211
+	/**
212
+	 * Sets the given headers to the message
213
+	 *
214
+	 * @param array<string, string|string[]> $headers
215
+	 *
216
+	 * @throws InvalidArgumentException
217
+	 */
218
+	final protected function setHeaders(array $headers): void
219
+	{
220
+		foreach ($headers as $name => $value) {
221
+			$this->setHeader($name, $value, false);
222
+		}
223
+	}
224
+
225
+	/**
226
+	 * Deletes a header from the message by the given name
227
+	 *
228
+	 * @param string $name
229
+	 */
230
+	final protected function deleteHeader($name): void
231
+	{
232
+		$key = strtolower($name);
233
+
234
+		if (isset($this->headerNames[$key])) {
235
+			unset($this->headers[$this->headerNames[$key]]);
236
+			unset($this->headerNames[$key]);
237
+		}
238
+	}
239
+
240
+	/**
241
+	 * Sets the given body to the message
242
+	 */
243
+	final protected function setBody(StreamInterface $body): void
244
+	{
245
+		$this->body = $body;
246
+	}
247
+
248
+	/**
249
+	 * Validates the given HTTP version
250
+	 *
251
+	 * @param mixed $protocolVersion
252
+	 *
253
+	 * @throws InvalidArgumentException
254
+	 */
255
+	private function validateProtocolVersion($protocolVersion): void
256
+	{
257
+		if ($protocolVersion === '') {
258
+			throw new InvalidArgumentException('HTTP version cannot be an empty');
259
+		}
260
+
261
+		if (!is_string($protocolVersion)) {
262
+			throw new InvalidArgumentException('HTTP version must be a string');
263
+		}
264
+
265
+		if (!preg_match(self::HTTP_VERSION_REGEX, $protocolVersion)) {
266
+			throw new InvalidArgumentException('HTTP version is invalid');
267
+		}
268
+	}
269
+
270
+	/**
271
+	 * Validates the given header name
272
+	 *
273
+	 * @param mixed $name
274
+	 *
275
+	 * @throws InvalidArgumentException
276
+	 */
277
+	private function validateHeaderName($name): void
278
+	{
279
+		if ($name === '') {
280
+			throw new InvalidArgumentException('HTTP header name cannot be an empty');
281
+		}
282
+
283
+		if (!is_string($name)) {
284
+			throw new InvalidArgumentException('HTTP header name must be a string');
285
+		}
286
+
287
+		if (!preg_match(HeaderInterface::RFC7230_TOKEN_REGEX, $name)) {
288
+			throw new InvalidArgumentException('HTTP header name is invalid');
289
+		}
290
+	}
291
+
292
+	/**
293
+	 * Validates the given header value
294
+	 *
295
+	 * @param array<array-key, mixed> $value
296
+	 *
297
+	 * @throws InvalidArgumentException
298
+	 */
299
+	private function validateHeaderValue(string $name, array $value): void
300
+	{
301
+		if ($value === []) {
302
+			throw new InvalidArgumentException("The value of the HTTP header $name cannot be an empty array");
303
+		}
304
+
305
+		foreach ($value as $key => $item) {
306
+			if ($item === '') {
307
+				continue;
308
+			}
309
+
310
+			if (!is_string($item)) {
311
+				throw new InvalidArgumentException("The value of the HTTP header $name:$key must be a string");
312
+			}
313
+
314
+			if (!preg_match(HeaderInterface::RFC7230_FIELD_VALUE_REGEX, $item)) {
315
+				throw new InvalidArgumentException("The value of the HTTP header $name:$key is invalid");
316
+			}
317
+		}
318
+	}
319 319
 }
Please login to merge, or discard this patch.