6
6
7
7
use Psr \Http \Message \ResponseFactoryInterface ;
8
8
use Psr \Http \Message \ResponseInterface ;
9
+ use Psr \Http \Message \StreamFactoryInterface ;
9
10
use Psr \Http \Message \StreamInterface ;
10
11
use RuntimeException ;
11
12
13
+ use function ftruncate ;
12
14
use function get_class ;
13
15
use function gettype ;
14
16
use function is_callable ;
15
17
use function is_object ;
18
+ use function is_resource ;
16
19
use function is_string ;
20
+ use function rewind ;
17
21
use function sprintf ;
18
22
19
23
/**
@@ -28,7 +32,14 @@ final class DataResponse implements ResponseInterface
28
32
* @var mixed
29
33
*/
30
34
private $ data ;
35
+
36
+ /**
37
+ * @var resource
38
+ */
39
+ private $ resource ;
40
+
31
41
private bool $ formatted = false ;
42
+ private bool $ forcedBody = false ;
32
43
private ResponseInterface $ response ;
33
44
private ?StreamInterface $ dataStream = null ;
34
45
private ?DataResponseFormatterInterface $ responseFormatter = null ;
@@ -38,11 +49,17 @@ final class DataResponse implements ResponseInterface
38
49
* @param int $code The response status code.
39
50
* @param string $reasonPhrase The response reason phrase associated with the status code.
40
51
* @param ResponseFactoryInterface $responseFactory The response factory instance.
52
+ * @param StreamFactoryInterface $streamFactory The stream factory instance.
41
53
*/
42
- public function __construct ($ data , int $ code , string $ reasonPhrase , ResponseFactoryInterface $ responseFactory )
43
- {
44
- $ this ->response = $ responseFactory ->createResponse ($ code , $ reasonPhrase );
54
+ public function __construct (
55
+ $ data ,
56
+ int $ code ,
57
+ string $ reasonPhrase ,
58
+ ResponseFactoryInterface $ responseFactory ,
59
+ StreamFactoryInterface $ streamFactory
60
+ ) {
45
61
$ this ->data = $ data ;
62
+ $ this ->createResponse ($ code , $ reasonPhrase , $ responseFactory , $ streamFactory );
46
63
}
47
64
48
65
public function getBody (): StreamInterface
@@ -52,18 +69,20 @@ public function getBody(): StreamInterface
52
69
}
53
70
54
71
if ($ this ->hasResponseFormatter ()) {
55
- $ this ->response = $ this -> formatResponse ();
72
+ $ this ->formatResponse ();
56
73
return $ this ->dataStream = $ this ->response ->getBody ();
57
74
}
58
75
59
76
if ($ this ->data === null ) {
77
+ $ this ->clearResponseBody ();
60
78
return $ this ->dataStream = $ this ->response ->getBody ();
61
79
}
62
80
63
81
/** @var mixed */
64
82
$ data = $ this ->getData ();
65
83
66
84
if (is_string ($ data )) {
85
+ $ this ->clearResponseBody ();
67
86
$ this ->response ->getBody ()->write ($ data );
68
87
return $ this ->dataStream = $ this ->response ->getBody ();
69
88
}
@@ -84,7 +103,7 @@ public function getBody(): StreamInterface
84
103
*/
85
104
public function getHeader ($ name ): array
86
105
{
87
- $ this ->response = $ this -> formatResponse ();
106
+ $ this ->formatResponse ();
88
107
return $ this ->response ->getHeader ($ name );
89
108
}
90
109
@@ -95,7 +114,7 @@ public function getHeader($name): array
95
114
*/
96
115
public function getHeaderLine ($ name ): string
97
116
{
98
- $ this ->response = $ this -> formatResponse ();
117
+ $ this ->formatResponse ();
99
118
return $ this ->response ->getHeaderLine ($ name );
100
119
}
101
120
@@ -106,25 +125,25 @@ public function getHeaderLine($name): string
106
125
*/
107
126
public function getHeaders (): array
108
127
{
109
- $ this ->response = $ this -> formatResponse ();
128
+ $ this ->formatResponse ();
110
129
return $ this ->response ->getHeaders ();
111
130
}
112
131
113
132
public function getProtocolVersion (): string
114
133
{
115
- $ this ->response = $ this -> formatResponse ();
134
+ $ this ->formatResponse ();
116
135
return $ this ->response ->getProtocolVersion ();
117
136
}
118
137
119
138
public function getReasonPhrase (): string
120
139
{
121
- $ this ->response = $ this -> formatResponse ();
140
+ $ this ->formatResponse ();
122
141
return $ this ->response ->getReasonPhrase ();
123
142
}
124
143
125
144
public function getStatusCode (): int
126
145
{
127
- $ this ->response = $ this -> formatResponse ();
146
+ $ this ->formatResponse ();
128
147
return $ this ->response ->getStatusCode ();
129
148
}
130
149
@@ -135,7 +154,7 @@ public function getStatusCode(): int
135
154
*/
136
155
public function hasHeader ($ name ): bool
137
156
{
138
- $ this ->response = $ this -> formatResponse ();
157
+ $ this ->formatResponse ();
139
158
return $ this ->response ->hasHeader ($ name );
140
159
}
141
160
@@ -165,6 +184,7 @@ public function withBody(StreamInterface $body): self
165
184
$ new = clone $ this ;
166
185
$ new ->response = $ this ->response ->withBody ($ body );
167
186
$ new ->dataStream = $ body ;
187
+ $ new ->forcedBody = true ;
168
188
$ new ->formatted = false ;
169
189
return $ new ;
170
190
}
@@ -195,7 +215,7 @@ public function withHeader($name, $value): self
195
215
public function withoutHeader ($ name ): self
196
216
{
197
217
$ new = clone $ this ;
198
- $ new ->response = $ new -> formatResponse ();
218
+ $ new ->formatResponse ();
199
219
$ new ->response = $ new ->response ->withoutHeader ($ name );
200
220
return $ new ;
201
221
}
@@ -267,12 +287,23 @@ public function hasResponseFormatter(): bool
267
287
*
268
288
* @param mixed $data The response data.
269
289
*
290
+ * @throws RuntimeException If the body was previously forced to be set {@see withBody()}.
291
+ *
270
292
* @return self
271
293
*/
272
294
public function withData ($ data ): self
273
295
{
296
+ if ($ this ->forcedBody ) {
297
+ throw new RuntimeException (sprintf (
298
+ 'The data cannot be set because the body was previously '
299
+ . ' forced to be set using the "%s::withBody()" method. ' ,
300
+ self ::class,
301
+ ));
302
+ }
303
+
274
304
$ new = clone $ this ;
275
305
$ new ->data = $ data ;
306
+ $ new ->dataStream = null ;
276
307
$ new ->formatted = false ;
277
308
return $ new ;
278
309
}
@@ -307,13 +338,11 @@ public function hasData(): bool
307
338
308
339
/**
309
340
* Formats the response, if necessary.
310
- *
311
- * @return ResponseInterface Formatted response.
312
341
*/
313
- private function formatResponse (): ResponseInterface
342
+ private function formatResponse (): void
314
343
{
315
- if (!$ this ->needFormatResponse ()) {
316
- return $ this -> response ;
344
+ if ($ this -> formatted || !$ this ->hasResponseFormatter ()) {
345
+ return ;
317
346
}
318
347
319
348
/** @psalm-var DataResponseFormatterInterface $this->responseFormatter */
@@ -330,25 +359,56 @@ private function formatResponse(): ResponseInterface
330
359
));
331
360
}
332
361
333
- return $ response ;
362
+ $ this -> response = $ response ;
334
363
}
335
364
336
365
/**
337
366
* Clears a response body.
338
367
*/
339
368
private function clearResponseBody (): void
340
369
{
341
- $ this ->response ->getBody ()->rewind ();
342
- $ this ->response ->getBody ()->write ('' );
370
+ if (!$ this ->forcedBody ) {
371
+ ftruncate ($ this ->resource , 0 );
372
+ rewind ($ this ->resource );
373
+ }
343
374
}
344
375
345
376
/**
346
- * Checks whether the response needs to be formatted .
377
+ * Creates a new response by retrieving and validating the stream resource .
347
378
*
348
- * @return bool Whether the response needs to be formatted.
379
+ * @param int $code The response status code.
380
+ * @param string $reasonPhrase The response reason phrase associated with the status code.
381
+ * @param ResponseFactoryInterface $responseFactory The response factory instance.
382
+ * @param StreamFactoryInterface $streamFactory The stream factory instance.
383
+ *
384
+ * @throws RuntimeException If the stream resource is not valid.
349
385
*/
350
- private function needFormatResponse (): bool
351
- {
352
- return $ this ->formatted === false && $ this ->hasResponseFormatter ();
386
+ private function createResponse (
387
+ int $ code ,
388
+ string $ reasonPhrase ,
389
+ ResponseFactoryInterface $ responseFactory ,
390
+ StreamFactoryInterface $ streamFactory
391
+ ): void {
392
+ $ response = $ responseFactory ->createResponse ($ code , $ reasonPhrase );
393
+ $ stream = $ response ->getBody ();
394
+
395
+ if (!$ stream ->isReadable ()) {
396
+ throw new RuntimeException ('Stream is not readable. ' );
397
+ }
398
+
399
+ if (!$ stream ->isSeekable ()) {
400
+ throw new RuntimeException ('Stream is not seekable. ' );
401
+ }
402
+
403
+ if (!$ stream ->isWritable ()) {
404
+ throw new RuntimeException ('Stream is not writable. ' );
405
+ }
406
+
407
+ if (!is_resource ($ resource = $ stream ->detach ())) {
408
+ throw new RuntimeException ('Resource was not separated from the stream. ' );
409
+ }
410
+
411
+ $ this ->resource = $ resource ;
412
+ $ this ->response = $ response ->withBody ($ streamFactory ->createStreamFromResource ($ this ->resource ));
353
413
}
354
414
}
0 commit comments