Passed
Push — master ( c6902f...770aa3 )
by smiley
02:51
created

Env   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 246
rs 8.3999
c 0
b 0
f 0
wmc 46

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __loadEnv() 0 7 3
B __getEnv() 0 21 5
A __setEnv() 0 21 3
B __read() 0 17 5
A __clearEnv() 0 9 2
B __check() 0 13 6
B __issetEnv() 0 8 6
A __unsetEnv() 0 11 2
B __parse() 0 25 4
B __load() 0 20 10

How to fix   Complexity   

Complex Class

Complex classes like Env often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Env, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Trait Env
4
 *
5
 * @filesource   Env.php
6
 * @created      25.11.2017
7
 * @package      chillerlan\Traits
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2017 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\Traits;
14
15
/**
16
 * Loads .env config files into the environment
17
 *
18
 * $_ENV > getenv()!
19
 *
20
 * @link https://github.com/vlucas/phpdotenv
21
 * @link http://php.net/variables-order
22
 *
23
 */
24
trait Env{
25
26
	/**
27
	 * a backup environment in case everything goes downhill
28
	 *
29
	 * @var array
30
	 */
31
	private $_ENV = [];
32
33
	/**
34
	 * Sets the global $_ENV if true. Otherwise all variables are being kept internally
35
	 * in $this->_ENV to avoid leaks, making them only accessible via Env::__getEnv().
36
	 *
37
	 * @var bool
38
	 */
39
	private $_global;
40
41
	/**
42
	 * @param string      $path
43
	 * @param string|null $filename
44
	 * @param bool|null   $overwrite
45
	 * @param array|null  $required
46
	 * @param bool|null   $global
47
	 *
48
	 * @return $this
49
	 */
50
	protected function __loadEnv(string $path, string $filename = null, bool $overwrite = null, array $required = null, bool $global = null){
51
		$this->_global = $global !== null ? $global : false;
52
		$content       = $this->__read(rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.($filename ?? '.env'));
53
54
		return $this
55
			->__load($content, $overwrite !== null ? $overwrite : false)
56
			->__check($required)
57
		;
58
	}
59
60
	/**
61
	 * @param string $var
62
	 *
63
	 * @return bool|mixed
64
	 */
65
	protected function __getEnv(string $var){
66
		$var = strtoupper($var);
67
		$env = null;
68
69
		if($this->_global === true){
70
71
			if(array_key_exists($var, $_ENV)){
72
				$env = $_ENV[$var];
73
			}
74
			elseif(function_exists('getenv')){
75
				$env = getenv($var);
76
			}
77
			// @codeCoverageIgnoreStart
78
			elseif(function_exists('apache_getenv')){
79
				$env = apache_getenv($var);
80
			}
81
			// @codeCoverageIgnoreEnd
82
83
		}
84
85
		return $env ?? $this->_ENV[$var] ?? false;
86
	}
87
88
	/**
89
	 * @param string $var
90
	 * @param string $value
91
	 *
92
	 * @return $this
93
	 */
94
	protected function __setEnv(string $var, string $value = null){
95
		$var   = strtoupper($var);
96
		$value = $this->__parse($value);
97
98
		if($this->_global === true){
99
			putenv($var.'='.$value);
100
101
			// fill $_ENV explicitly, assuming variables_order="GPCS" (production)
102
			$_ENV[$var] = $value;
103
104
			// @codeCoverageIgnoreStart
105
			if(function_exists('apache_setenv')){
106
				apache_setenv($var, $value);
107
			}
108
			// @codeCoverageIgnoreEnd
109
		}
110
111
		// a backup
112
		$this->_ENV[$var] = $value;
113
114
		return $this;
115
	}
116
117
	/**
118
	 * @param string $var
119
	 *
120
	 * @return bool
121
	 */
122
	public function __issetEnv(string $var):bool {
123
		return
124
			($this->_global && (
125
				isset($_ENV[$var])
126
				|| getenv($var)
127
				|| (function_exists('apache_getenv') && apache_getenv($var))
128
			))
129
			|| array_key_exists($var, $this->_ENV);
130
	}
131
132
	/**
133
	 * @param string $var
134
	 *
135
	 * @return $this
136
	 */
137
	protected function __unsetEnv(string $var){
138
		$var = strtoupper($var);
139
140
		if($this->_global === true){
141
			unset($_ENV[$var]);
142
			putenv($var);
143
		}
144
145
		unset($this->_ENV[$var]);
146
147
		return $this;
148
	}
149
150
	/**
151
	 * use with caution!
152
	 *
153
	 * @return $this
154
	 */
155
	protected function __clearEnv(){
156
157
		if($this->_global === true){
158
			$_ENV = [];
159
		}
160
161
		$this->_ENV = [];
162
163
		return $this;
164
	}
165
166
	/**
167
	 * @param string $file
168
	 *
169
	 * @return array
170
	 * @throws \chillerlan\Traits\TraitException
171
	 */
172
	private function __read(string $file):array{
173
174
		if(!is_readable($file) || !is_file($file)){
175
			throw new TraitException('invalid file: '.$file);
176
		}
177
178
		// Read file into an array of lines with auto-detected line endings
179
		$autodetect = ini_get('auto_detect_line_endings');
180
		ini_set('auto_detect_line_endings', '1');
181
		$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
182
		ini_set('auto_detect_line_endings', $autodetect);
183
184
		if(!is_array($lines) || empty($lines)){
185
			throw new TraitException('error while reading file: '.$file);
186
		}
187
188
		return array_map('trim', $lines);
189
	}
190
191
	/**
192
	 * @param array $data
193
	 * @param bool  $overwrite
194
	 *
195
	 * @return $this
196
	 */
197
	private function __load(array $data, bool $overwrite){
198
199
		foreach($data as $line){
200
201
			// skip empty lines and comments
202
			if(empty($line) || strpos($line, '#') === 0){
203
				continue;
204
			}
205
206
			$kv = array_map('trim', explode('=', $line, 2));
207
208
			// skip empty and numeric keys, keys with spaces, existing keys that shall not be overwritten
209
			if(empty($kv[0]) || is_numeric($kv[0]) || strpos($kv[0], ' ') !== false || (!$overwrite && $this->__getEnv($kv[0]) !== false)){
210
				continue;
211
			}
212
213
			$this->__setEnv($kv[0], isset($kv[1]) ? trim($kv[1]) : null);
214
		}
215
216
		return $this;
217
	}
218
219
	/**
220
	 * @param string $value
221
	 *
222
	 * @return string|null
223
	 */
224
	private function __parse(string $value = null){
225
226
		if($value !== null){
227
228
			$q = $value[0] ?? null;
229
230
			$value = in_array($q, ["'", '"'], true)
231
				// handle quoted strings
232
				? preg_replace("/^$q((?:[^$q\\\\]|\\\\\\\\|\\\\$q)*)$q.*$/mx", '$1', $value)
233
				// skip inline comments
234
				: trim(explode('#', $value, 2)[0]);
235
236
			// handle multiline values
237
			$value = implode(PHP_EOL, explode('\\n', $value));
238
239
			// handle nested ${VARS}
240
			if(strpos($value, '$') !== false){
241
				$value = preg_replace_callback('/\${(?<var>[_a-z\d]+)}/i', function($matches){
242
					return $this->__getEnv($matches['var']);
243
				}, $value);
244
			}
245
246
		}
247
248
		return $value;
249
	}
250
251
	/**
252
	 * @param array|null $required
253
	 *
254
	 * @return $this
255
	 * @throws \chillerlan\Traits\TraitException
256
	 */
257
	private function __check(array $required = null){
258
259
		if($required === null || empty($required)){
260
			return $this;
261
		}
262
263
		foreach($required as $var){
264
			if($this->__getEnv($var) === false || $this->__getEnv($var) === null){
265
				throw new TraitException('required variable not set: '.strtoupper($var));
266
			}
267
		}
268
269
		return $this;
270
	}
271
}
272