Passed
Push — master ( e307b8...bd14b3 )
by Atanas
02:12
created

ResponseService::view()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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