Completed
Push — master ( 183a1c...44c9b3 )
by Chris
02:36
created

Response::headersSent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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