Response::content()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 11
rs 10
1
<?php
2
namespace Darya\Http;
3
4
/**
5
 * Darya's HTTP response representation.
6
 *
7
 * TODO: Support content streams.
8
 *
9
 * @property-read Cookies $cookies
10
 *
11
 * @author Chris Andrew <[email protected]>
12
 */
13
class Response
14
{
15
	/**
16
	 * HTTP status code.
17
	 *
18
	 * @var int
19
	 */
20
	private $status = 200;
21
22
	/**
23
	 * HTTP headers.
24
	 *
25
	 * @var array
26
	 */
27
	private $headers = array();
28
29
	/**
30
	 * Cookie key/values.
31
	 *
32
	 * @var Cookies
33
	 */
34
	private $cookies;
35
36
	/**
37
	 * Response content.
38
	 *
39
	 * @var string
40
	 */
41
	private $content = null;
42
43
	/**
44
	 * Whether the response headers have been sent.
45
	 *
46
	 * @var bool
47
	 */
48
	private $headersSent = false;
49
50
	/**
51
	 * Whether the response content has been sent.
52
	 *
53
	 * @var bool
54
	 */
55
	private $contentSent = false;
56
57
	/**
58
	 * Whether the response has been redirected.
59
	 *
60
	 * @var bool
61
	 */
62
	private $redirected = false;
63
64
	/**
65
	 * Properties that can be read dynamically.
66
	 *
67
	 * @var array
68
	 */
69
	private $properties = array(
70
		'status', 'headers', 'cookies', 'content', 'redirected'
71
	);
72
73
	/**
74
	 * Prepare the given response content as a string.
75
	 *
76
	 * Invokes `__toString()` on objects if exposed. Encodes arrays as JSON.
77
	 * Anything else is casted to a string.
78
	 *
79
	 * @param mixed $content
80
	 * @return string
81
	 */
82
	public static function prepareContent($content)
83
	{
84
		if (is_object($content) && method_exists($content, '__toString')) {
85
			$content = $content->__toString();
86
		} else if (is_array($content)) {
87
			$content = json_encode($content);
88
		} else {
89
			$content = (string) $content;
90
		}
91
92
		return $content;
93
	}
94
95
	/**
96
	 * Instantiate a new response with the optionally given content and headers.
97
	 *
98
	 * @param mixed $content [optional]
99
	 * @param array $headers [optional]
100
	 */
101
	public function __construct($content = null, array $headers = array())
102
	{
103
		if ($content !== null) {
104
			$this->content($content);
105
		}
106
107
		$this->headers($headers);
108
109
		$this->cookies = new Cookies;
0 ignored issues
show
Bug introduced by
The property cookies is declared read-only in Darya\Http\Response.
Loading history...
110
111
		$this->properties = array_flip($this->properties);
112
	}
113
114
	/**
115
	 * Dynamically retrieve a property.
116
	 *
117
	 * @param string $property
118
	 * @return mixed
119
	 */
120
	public function __get($property)
121
	{
122
		if (isset($this->properties[$property])) {
123
			return $this->$property;
124
		}
125
	}
126
127
	/**
128
	 * Get and optionally set the HTTP status code of the response.
129
	 *
130
	 * @param int|string $status [optional]
131
	 * @return int
132
	 */
133
	public function status($status = null)
134
	{
135
		if (is_numeric($status)) {
136
			$this->status = (int) $status;
137
		}
138
139
		return $this->status;
140
	}
141
142
	/**
143
	 * Add a header to send with the response.
144
	 *
145
	 * @param string $header
146
	 */
147
	public function header($header)
148
	{
149
		$header = (string) $header;
150
151
		if (strlen($header)) {
152
			list($name, $value) = array_pad(explode(':', $header, 2), 2, null);
153
			$this->headers[$name] = ltrim($value);
154
		}
155
	}
156
157
	/**
158
	 * Retrieve and optionally add headers to send with the response.
159
	 *
160
	 * @param array|string $headers [optional]
161
	 * @return array
162
	 */
163
	public function headers($headers = array())
164
	{
165
		foreach ((array) $headers as $header) {
166
			$this->header($header);
167
		}
168
169
		return $this->headers;
170
	}
171
172
	/**
173
	 * Get and optionally set the response content.
174
	 *
175
	 * @param mixed $content [optional]
176
	 * @return string
177
	 */
178
	public function content($content = null)
179
	{
180
		if (is_array($content)) {
181
			$this->header('Content-Type: application/json');
182
		}
183
184
		if ($content !== null) {
185
			$this->content = $content;
186
		}
187
188
		return $this->content;
189
	}
190
191
	/**
192
	 * Retrieve the response content as a string.
193
	 *
194
	 * @return string
195
	 */
196
	public function body()
197
	{
198
		return static::prepareContent($this->content);
199
	}
200
201
	/**
202
	 * Determines whether any response content has been set.
203
	 *
204
	 * @return bool
205
	 */
206
	public function hasContent()
207
	{
208
		return $this->content !== null && $this->content !== false;
209
	}
210
211
	/**
212
	 * Redirect the response to another location.
213
	 *
214
	 * This redirect will only happen when the response headers have been sent.
215
	 *
216
	 * @param string $url
217
	 * @return $this
218
	 */
219
	public function redirect($url)
220
	{
221
		$this->header("Location: $url");
222
		$this->redirected = true;
223
224
		return $this;
225
	}
226
227
	/**
228
	 * Determines whether the response has been redirected or not.
229
	 *
230
	 * @return bool
231
	 */
232
	public function redirected()
233
	{
234
		return $this->redirected;
235
	}
236
237
	/**
238
	 * Determine whether any headers have been sent by this response or another.
239
	 *
240
	 * @return bool
241
	 */
242
	protected function headersSent()
243
	{
244
		return $this->headersSent || headers_sent();
245
	}
246
247
	/**
248
	 * Determine whether the response content has been sent.
249
	 *
250
	 * @return bool
251
	 */
252
	 protected function contentSent()
253
	 {
254
	 	return $this->contentSent;
255
	 }
256
257
	/**
258
	 * Sends the current HTTP status of the response.
259
	 */
260
	protected function sendStatus()
261
	{
262
		if (function_exists('http_response_code')) {
263
			http_response_code($this->status);
264
		} else {
265
			header(':', true, $this->status);
266
		}
267
	}
268
269
	/**
270
	 * Sends all the currently set cookies.
271
	 */
272
	protected function sendCookies()
273
	{
274
		$this->cookies->send();
275
	}
276
277
	/**
278
	 * Send the response headers to the client, provided that they have not yet
279
	 * been sent.
280
	 *
281
	 * HTTP status and cookies are sent before the headers.
282
	 *
283
	 * @return bool
284
	 */
285
	public function sendHeaders()
286
	{
287
		if (!$this->headersSent()) {
288
			$this->sendStatus();
289
			$this->sendCookies();
290
291
			foreach ($this->headers as $name => $value) {
292
				header("$name: $value", true);
293
			}
294
295
			$this->headersSent = true;
296
		}
297
298
		return $this->headersSent;
299
	}
300
301
	/**
302
	 * Send the response content to the client.
303
	 *
304
	 * This will only succeed if response headers have been sent, response
305
	 * content has not yet been sent, and the response has not been redirected.
306
	 *
307
	 * @return bool
308
	 */
309
	public function sendContent()
310
	{
311
		if ($this->headersSent() && !$this->contentSent() && !$this->redirected) {
312
			echo $this->body();
313
314
			$this->contentSent = true;
315
		}
316
317
		return $this->contentSent();
318
	}
319
320
	/**
321
	 * Sends the response to the client.
322
	 *
323
	 * Sends the response headers and response content.
324
	 *
325
	 * If the response has been redirected, only headers will be sent.
326
	 */
327
	public function send()
328
	{
329
		$this->sendHeaders();
330
331
		if (!$this->redirected) {
332
			$this->sendContent();
333
		}
334
	}
335
}
336