Completed
Push — master ( 4474c7...9ddc72 )
by Chris
06:56
created

Response   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 7
Bugs 2 Features 3
Metric Value
wmc 37
c 7
b 2
f 3
lcom 1
cbo 1
dl 0
loc 290
rs 8.6

18 Methods

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