Completed
Push — master ( 32eaad...6cdc53 )
by Nazar
04:10
created

Response   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 54.76%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 207
ccs 46
cts 84
cp 0.5476
rs 9.8
c 1
b 0
f 0
wmc 31
lcom 1
cbo 5

8 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 11 3
A init_with_typical_default_settings() 0 13 1
A header() 0 11 4
A redirect() 0 6 1
A output_default() 0 8 2
A output_default_cli() 0 13 4
B output_default_web() 0 19 7
D cookie() 0 35 9
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
use
10
	cs\Response\Psr7;
11
use function
12
	cli\out;
13
14
class Response {
15
	use
16
		Singleton,
17
		Psr7;
18
	/**
19
	 * Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
20
	 *
21
	 * @var string
22
	 */
23
	public $protocol;
24
	/**
25
	 * HTTP status code
26
	 *
27
	 * @var int
28
	 */
29
	public $code;
30
	/**
31
	 * Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language`
32
	 *
33
	 * Values might be strings in case of single value or array of strings in case of multiple values with the same field name
34
	 *
35
	 * @var string[][]
36
	 */
37
	public $headers;
38
	/**
39
	 * String body (is used instead of `$this->body_stream` in most cases, ignored if `$this->body_stream` is present)
40
	 *
41
	 * @var string
42
	 */
43
	public $body;
44
	/**
45
	 * Body in form of stream (might be used instead of `$this->body` in some cases, if present, `$this->body` is ignored)
46
	 *
47
	 * Stream is read/write
48
	 *
49
	 * @var resource
50
	 */
51
	public $body_stream;
52
	/**
53
	 * Initialize response object with specified data
54
	 *
55
	 * @param string               $body
56
	 * @param null|resource|string $body_stream String, like `php://temp` or resource, like `fopen('php://temp', 'a+b')`, if present, `$body` is ignored
57
	 * @param string[]|string[][]  $headers     Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`,
58
	 *                                          `content-type`, `accept-language`; Values might be strings in case of single value or array of strings in case
59
	 *                                          of multiple values with the same field name
60
	 * @param int                  $code        HTTP status code
61
	 * @param string               $protocol    Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
62
	 *
63
	 * @return Response
64
	 */
65 8
	function init ($body = '', $body_stream = null, $headers = [], $code = 200, $protocol = 'HTTP/1.1') {
66 8
		$this->protocol = $protocol;
67 8
		$this->code     = $code;
68 8
		$this->headers  = _array($headers);
69 8
		$this->body     = $body;
70 8
		if ($this->body_stream) {
71
			fclose($this->body_stream);
72
		}
73 8
		$this->data_stream = is_string($body_stream) ? fopen($body_stream, 'a+b') : $body_stream;
74 8
		return $this;
75
	}
76
	/**
77
	 * Initialize with typical default settings (headers `Content-Type`, `Vary` and `X-UA-Compatible`, protocol taken from `cs\Request::$protocol`)
78
	 *
79
	 * @return Response
80
	 */
81 8
	function init_with_typical_default_settings () {
82 8
		return $this->init(
83 8
			'',
84 8
			null,
85
			[
86 8
				'Content-Type'    => 'text/html; charset=utf-8',
87
				'Vary'            => 'Accept-Language,User-Agent,Cookie',
88
				'X-UA-Compatible' => 'IE=edge' // TODO: I hope some day we'll get rid of this sh*t :(
89
			],
90 8
			200,
91 8
			Request::instance()->protocol
92
		);
93
	}
94
	/**
95
	 * Set raw HTTP header
96
	 *
97
	 * @param string $field        Field
98
	 * @param string $value        Value, empty string will cause header removal
99
	 * @param bool   $replace      The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header
100
	 *                             of the same type. By default it will replace
101
	 *
102
	 * @return Response
103
	 */
104 34
	function header ($field, $value, $replace = true) {
105 34
		$field = strtolower($field);
106 34
		if ($value === '') {
107
			unset($this->headers[$field]);
108 34
		} elseif ($replace || !isset($this->headers[$field])) {
109 34
			$this->headers[$field] = [$value];
110
		} else {
111 8
			$this->headers[$field][] = $value;
112
		}
113 34
		return $this;
114
	}
115
	/**
116
	 * Make redirect to specified location
117
	 *
118
	 * @param string $location
119
	 * @param int    $code
120
	 *
121
	 * @return Response
122
	 */
123
	function redirect ($location, $code = 302) {
124
		$this->header('location', $location);
125
		$this->code                 = $code;
126
		Page::instance()->interface = false;
127
		return $this;
128
	}
129
	/**
130
	 * Function for setting cookies, taking into account cookies prefix. Parameters like in system `setcookie()` function, but `$path`, `$domain` and `$secure`
131
	 * are skipped, they are detected automatically
132
	 *
133
	 * This function have side effect of setting cookie on `cs\Request` object
134
	 *
135
	 * @param string $name
136
	 * @param string $value
137
	 * @param int    $expire
138
	 * @param bool   $httponly
139
	 *
140
	 * @return Response
141
	 */
142 10
	function cookie ($name, $value, $expire = 0, $httponly = false) {
143 10
		$Request = Request::instance();
144 10
		$Config  = Config::instance();
145 10
		$prefix  = '';
146 10
		$domain  = explode(':', $Request->host)[0];
147 10
		if ($Config) {
148 10
			$prefix         = $Config->core['cookie_prefix'];
149 10
			$cookie_domains = $Config->core['cookie_domain'];
150 10
			$domain         = isset($cookie_domains[$Request->mirror_index]) ? $cookie_domains[$Request->mirror_index] : $cookie_domains[0];
151
		}
152 10
		if ($value === '') {
153 6
			unset($Request->cookie[$name], $Request->cookie[$prefix.$name]);
154
		} else {
155 10
			$Request->cookie[$name]         = $value;
156 10
			$Request->cookie[$prefix.$name] = $value;
157
		}
158
		$header = [
159 10
			rawurlencode($prefix.$name).'='.rawurlencode($value),
160 10
			'path=/'
161
		];
162 10
		if ($expire || !$value) {
163 10
			$header[] = 'expires='.gmdate('D, d-M-Y H:i:s', $expire).' GMT';
164
		}
165 10
		if ($domain) {
166 10
			$header[] = "domain=$domain";
167
		}
168 10
		if ($Request->secure) {
169
			$header[] = 'secure';
170
		}
171 10
		if ($httponly) {
172 10
			$header[] = 'HttpOnly';
173
		}
174 10
		$this->header('set-cookie', implode('; ', $header), false);
175 10
		return $this;
176
	}
177
	/**
178
	 * Provides default output for all the response data using `header()`, `http_response_code()` and `echo` or `php://output`
179
	 */
180
	function output_default () {
181
		ob_implicit_flush(true);
182
		if (Request::instance()->cli_path) {
183
			$this->output_default_cli();
184
		} else {
185
			$this->output_default_web();
186
		}
187
	}
188
	protected function output_default_cli () {
189
		if ($this->code >= 400 && $this->code <= 510) {
190
			out($this->body);
191
			exit($this->code % 256);
192
		}
193
		if (is_resource($this->body_stream)) {
194
			$position = ftell($this->body_stream);
195
			stream_copy_to_stream($this->body_stream, STDIN);
196
			fseek($this->body_stream, $position);
197
		} else {
198
			out($this->body);
199
		}
200
	}
201
	protected function output_default_web () {
202
		foreach ($this->headers ?: [] as $header => $value) {
203
			$header = ucwords($header, '-');
204
			foreach ($value as $v) {
205
				header("$header: $v", false);
206
			}
207
		}
208
		http_response_code($this->code);
209
		if ($this->code >= 300 && $this->code < 400) {
210
			return;
211
		}
212
		if (is_resource($this->body_stream)) {
213
			$position = ftell($this->body_stream);
214
			stream_copy_to_stream($this->body_stream, fopen('php:://output', 'wb'));
215
			fseek($this->body_stream, $position);
216
		} else {
217
			echo $this->body;
218
		}
219
	}
220
}
221