Completed
Push — master ( 122153...1ebbbc )
by Nazar
04:18
created

Response   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 25
c 3
b 1
f 1
lcom 1
cbo 4
dl 0
loc 158
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 10 3
A header() 0 10 4
B redirect() 0 8 5
D cookie() 0 37 9
A standard_output() 0 16 4
1
<?php
2
/**
3
 * @package   CleverStyle CMS
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
10
class Response {
11
	use
12
		Singleton;
13
	/**
14
	 * Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
15
	 *
16
	 * @var string
17
	 */
18
	public $protocol;
19
	/**
20
	 * HTTP status code
21
	 *
22
	 * @var int
23
	 */
24
	public $code;
25
	/**
26
	 * Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`, `content-type`, `accept-language`
27
	 *
28
	 * Values might be strings in case of single value or array of strings in case of multiple values with the same field name
29
	 *
30
	 * @var string[]|string[][]
31
	 */
32
	public $headers;
33
	/**
34
	 * String body (is used instead of `$this->body_stream` in most cases)
35
	 *
36
	 * @var string
37
	 */
38
	public $body;
39
	/**
40
	 * Body in form of stream (might be used instead of `$this->body` in some cases, if present, `$this->body` is ignored)
41
	 *
42
	 * Stream is read/write
43
	 *
44
	 * @var resource
45
	 */
46
	public $body_stream;
47
	/**
48
	 * Initialize response object with specified data
49
	 *
50
	 * @param string               $body
51
	 * @param null|resource|string $body_stream String, like `php://temp` or resource, like `fopen('php://temp', 'ba+')`, if present, `$body` is ignored
52
	 * @param string[]|string[][]  $headers     Headers are normalized to lowercase keys with hyphen as separator, for instance: `connection`, `referer`,
53
	 *                                          `content-type`, `accept-language`; Values might be strings in case of single value or array of strings in case
54
	 *                                          of multiple values with the same field name
55
	 * @param int                  $code        HTTP status code
56
	 * @param string               $protocol    Protocol, for instance: `HTTP/1.0`, `HTTP/1.1` (default), HTTP/2.0
57
	 */
58
	function init ($body = '', $body_stream = null, $headers = [], $code = 200, $protocol = 'HTTP/1.1') {
59
		$this->protocol = $protocol;
60
		$this->code     = $code;
61
		$this->headers  = $headers;
62
		$this->body     = $body;
63
		if ($this->body_stream) {
64
			fclose($this->body_stream);
65
		}
66
		$this->data_stream = is_string($body_stream) ? fopen($body_stream, 'ba+') : $body_stream;
67
	}
68
	/**
69
	 * Set raw HTTP header
70
	 *
71
	 * @param string $field        Field
72
	 * @param string $value        Value, empty string will cause header removal
73
	 * @param bool   $replace      The optional replace parameter indicates whether the header should replace a previous similar header, or add a second header
74
	 *                             of the same type. By default it will replace
75
	 */
76
	function header ($field, $value, $replace = true) {
77
		$field = strtolower($field);
78
		if ($value === '') {
79
			unset($this->headers[$field]);
80
		} elseif ($replace || !isset($this->headers[$field])) {
81
			$this->headers[$field] = [$value];
82
		} else {
83
			$this->headers[$field][] = $value;
84
		}
85
	}
86
	/**
87
	 * Make redirect to specified location
88
	 *
89
	 * @param string   $location
90
	 * @param int|null $code Defaults to 302 if current code is not 201 or 3xx
91
	 */
92
	function redirect ($location, $code = null) {
93
		$this->header('location', $location);
94
		if ($code !== null) {
95
			$this->code = $code;
96
		} elseif ($this->code !== 201 && $this->code >= 300 && $this->code < 400) {
97
			$this->code = 302;
98
		}
99
	}
100
	/**
101
	 * Function for setting cookies, taking into account cookies prefix. Parameters like in system `setcookie()` function, but $path, $domain and $secure
102
	 * are skipped, they are detected automatically
103
	 *
104
	 * This function have side effect of setting cookie on `cs\Request` object
105
	 *
106
	 * @param string $name
107
	 * @param string $value
108
	 * @param int    $expire
109
	 * @param bool   $httponly
110
	 */
111
	function cookie ($name, $value, $expire = 0, $httponly = false) {
112
		$Request = Request::instance();
113
		$Config  = Config::instance();
114
		$prefix  = '';
115
		$secure  = $Request->secure;
116
		$domain  = explode(':', $Request->host)[0];
117
		if ($Config) {
118
			$Route          = Route::instance();
119
			$prefix         = $Config->core['cookie_prefix'];
120
			$cookie_domains = $Config->core['cookie_domain'];
121
			/** @noinspection OffsetOperationsInspection */
122
			$domain = isset($cookie_domains[$Route->mirror_index]) ? $cookie_domains[$Route->mirror_index] : $cookie_domains[0];
123
		}
124
		if ($value === '') {
125
			unset($Request->cookie[$name], $Request->cookie[$prefix.$name]);
126
		} else {
127
			$Request->cookie[$name]         = $value;
128
			$Request->cookie[$prefix.$name] = $value;
129
		}
130
		$header = [
131
			rawurlencode($prefix.$name).'='.rawurlencode($value),
132
			'path=/'
133
		];
134
		if ($expire || !$value) {
135
			$header[] = 'expires='.gmdate('D, d-M-Y H:i:s', $expire).' GMT';
136
		}
137
		if ($domain) {
138
			$header[] = "domain=$domain";
139
		}
140
		if ($secure) {
141
			$header[] = 'secure';
142
		}
143
		if ($httponly) {
144
			$header[] = 'HttpOnly';
145
		}
146
		$this->header('Set-Cookie', implode('; ', $header), false);
147
	}
148
	/**
149
	 * Provides standard output for all the response data
150
	 */
151
	function standard_output () {
152
		foreach ($this->headers as $header => $value) {
153
			foreach ($value as $v) {
0 ignored issues
show
Bug introduced by
The expression $value of type string|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
154
				header("$header: $v", false);
155
			}
156
		}
157
		http_response_code($this->code);
158
		if (is_resource($this->body_stream)) {
159
			$position = ftell($this->body_stream);
160
			rewind($this->body_stream);
161
			stream_copy_to_stream($this->body_stream, fopen('php:://output', 'w'));
162
			fseek($this->body_stream, $position);
163
		} else {
164
			echo $this->body;
165
		}
166
	}
167
}
168