Passed
Push — master ( d2008b...aa9f10 )
by Atanas
01:54
created

ResponseService::sendHeaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
241 1
		if ( $status === 404 ) {
242 1
			$wp_query->set_404();
243 1
		}
244
245 1
		return $this::view( [$status, 'error', 'index'] )
246 1
			->toResponse()
247 1
			->withStatus( $status );
248
	}
249
}
250