ResponseService::getBodyContentLength()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 4
nop 1
dl 0
loc 13
ccs 0
cts 0
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   WPEmerge
4
 * @author    Atanas Angelov <[email protected]>
5
 * @copyright 2017-2019 Atanas Angelov
6
 * @license   https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0
7
 * @link      https://wpemerge.com/
8
 */
9
10
namespace WPEmerge\Responses;
11
12
use GuzzleHttp\Psr7;
13
use GuzzleHttp\Psr7\Response as Psr7Response;
14
use WPEmerge\Requests\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\StreamInterface;
17
use WPEmerge\View\ViewService;
18
19
/**
20
 * A collection of tools for the creation of responses.
21
 */
22
class ResponseService {
23
	/**
24
	 * Current request.
25
	 *
26
	 * @var RequestInterface
27
	 */
28
	protected $request = null;
29
30
	/**
31
	 * View service.
32
	 *
33
	 * @var ViewService
34
	 */
35
	protected $view_service = null;
36
37
	/**
38
	 * Constructor.
39
	 *
40
	 * @codeCoverageIgnore
41
	 * @param RequestInterface $request
42
	 * @param ViewService      $view_service
43
	 */
44
	public function __construct( RequestInterface $request, ViewService $view_service ) {
45
		$this->request = $request;
46
		$this->view_service = $view_service;
47
	}
48
49
	/**
50
	 * Send output based on a response object.
51
	 * @credit heavily modified version of slimphp/slim - Slim/App.php
52
	 *
53
	 * @codeCoverageIgnore
54
	 * @param  ResponseInterface $response
55
	 * @return void
56
	 */
57
	public function respond( ResponseInterface $response ) {
58
		if ( ! headers_sent() ) {
59
			$this->sendHeaders( $response );
60
		}
61
		$this->sendBody( $response );
62
	}
63
64
	/**
65
	 * Send a request's headers to the client.
66
	 *
67
	 * @codeCoverageIgnore
68
	 * @param  ResponseInterface $response
69
	 * @return void
70
	 */
71
	public function sendHeaders( ResponseInterface $response ) {
72
		// Status
73
		header( sprintf(
74
			'HTTP/%s %s %s',
75
			$response->getProtocolVersion(),
76
			$response->getStatusCode(),
77
			$response->getReasonPhrase()
78
		) );
79
80
		// Headers
81
		foreach ( $response->getHeaders() as $name => $values ) {
82
			foreach ( $values as $i => $value ) {
83
				header( sprintf( '%s: %s', $name, $value ), $i === 0 );
84
			}
85
		}
86
	}
87
88
	/**
89
	 * Get a response's body stream so it is ready to be read.
90
	 *
91
	 * @codeCoverageIgnore
92
	 * @param  ResponseInterface $response
93
	 * @return StreamInterface
94
	 */
95
	protected function getBody( ResponseInterface $response ) {
96
		$body = $response->getBody();
97
		if ( $body->isSeekable() ) {
98
			$body->rewind();
99
		}
100
		return $body;
101
	}
102
103
	/**
104
	 * Get a response's body's content length.
105
	 *
106
	 * @codeCoverageIgnore
107
	 * @param  ResponseInterface $response
108
	 * @return integer
109
	 */
110
	protected function getBodyContentLength( ResponseInterface $response ) {
111
		$content_length = $response->getHeaderLine( 'Content-Length' );
112
113
		if ( ! $content_length ) {
114
			$body = $this->getBody( $response );
115
			$content_length = $body->getSize();
116
		}
117
118
		if ( ! is_numeric( $content_length ) ) {
119
			$content_length = 0;
120
		}
121
122
		return (integer) $content_length;
123
	}
124
125
	/**
126
	 * Send a request's body to the client.
127
	 *
128
	 * @codeCoverageIgnore
129
	 * @param  ResponseInterface $response
130
	 * @param  integer           $chunk_size
131
	 * @return void
132
	 */
133
	public function sendBody( ResponseInterface $response, $chunk_size = 4096 ) {
134
		$body = $this->getBody( $response );
135
		$content_length = $this->getBodyContentLength( $response );
136
137
		if ( $content_length > 0 ) {
138
			$this->sendBodyWithLength( $body, $content_length, $chunk_size );
139
		} else {
140
			$this->sendBodyWithoutLength( $body, $chunk_size );
141
		}
142
	}
143
144
	/**
145
	 * Send a body with an unknown length to the client.
146
	 *
147
	 * @codeCoverageIgnore
148
	 * @param  StreamInterface $body
149
	 * @param  integer         $chunk_size
150
	 * @return void
151
	 */
152
	protected function sendBodyWithoutLength( StreamInterface $body, $chunk_size ) {
153
		while ( connection_status() === CONNECTION_NORMAL && ! $body->eof() ) {
154
			echo $body->read( $chunk_size );
155
		}
156
	}
157
158
	/**
159
	 * Send a body with a known length to the client.
160
	 *
161
	 * @codeCoverageIgnore
162
	 * @param  StreamInterface $body
163
	 * @param  integer         $length
164
	 * @param  integer         $chunk_size
165
	 * @return void
166
	 */
167
	protected function sendBodyWithLength( StreamInterface $body, $length, $chunk_size ) {
168
		$content_left = $length;
169
170
		while ( connection_status() === CONNECTION_NORMAL && $content_left > 0 ) {
171
			$read = min( $chunk_size, $content_left );
172
173
			if ( $read <= 0 ) {
174
				break;
175
			}
176
177
			echo $body->read( $read );
178
179
			$content_left -= $read;
180
		}
181
	}
182
183
	/**
184
	 * Create a new response object.
185
	 *
186
	 * @return ResponseInterface
187
	 */
188 1
	public function response() {
189 1
		return new Psr7Response();
190
	}
191
192
	/**
193
	 * Get a cloned response with the passed string as the body.
194
	 *
195
	 * @param  string            $output
196
	 * @return ResponseInterface
197
	 */
198 1
	public function output( $output ) {
199 1
		$response = $this->response();
200 1
		$response = $response->withBody( Psr7\stream_for( $output ) );
201 1
		return $response;
202
	}
203
204
	/**
205
	 * Get a cloned response, json encoding the passed data as the body.
206
	 *
207
	 * @param  mixed             $data
208
	 * @return ResponseInterface
209
	 */
210 1
	public function json( $data ) {
211 1
		$response = $this->response();
212 1
		$response = $response->withHeader( 'Content-Type', 'application/json' );
213 1
		$response = $response->withBody( Psr7\stream_for( wp_json_encode( $data ) ) );
214 1
		return $response;
215
	}
216
217
	/**
218
	 * Get a cloned response, with location and status headers.
219
	 *
220
	 * @param  RequestInterface|null $request
221
	 * @return RedirectResponse
222
	 */
223 1
	public function redirect( RequestInterface $request = null ) {
224 1
		$request = $request ? $request : $this->request;
225 1
		return new RedirectResponse( $request );
226
	}
227
228
	/**
229
	 * Get an error response, with status headers and rendering a suitable view as the body.
230
	 *
231
	 * @param  integer           $status
232
	 * @return ResponseInterface
233
	 */
234 3
	public function error( $status ) {
235 3
		$views = [$status, 'error', 'index'];
236
237 3
		if ( is_admin() ) {
238 2
			$views = array_merge(
239
				array_map( function ( $view ) {
240 2
					return $view . '-' . ( wp_doing_ajax() ? 'ajax' : 'admin' );
241 2
				}, $views ),
242
				$views
243
			);
244
		}
245
246 3
		return $this->view_service->make( $views )
247 3
			->toResponse()
248 3
			->withStatus( $status );
249
	}
250
}
251