Passed
Push — master ( 22a5db...795c19 )
by smiley
02:05
created

Env   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 179
rs 9.6
c 0
b 0
f 0
wmc 32

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __loadEnv() 0 7 2
B __read() 0 17 5
A __getEnv() 0 4 2
A __clearEnv() 0 4 1
B __check() 0 13 6
A __unsetEnv() 0 7 1
B __parse() 0 25 4
A __setEnv() 0 9 1
B __load() 0 20 10
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
 */
22
trait Env{
23
24
	/**
25
	 * @param string      $path
26
	 * @param string|null $filename
27
	 * @param bool|null   $overwrite
28
	 * @param array|null  $required
29
	 *
30
	 * @return $this
31
	 */
32
	protected function __loadEnv(string $path, string $filename = null, bool $overwrite = null, array $required = null){
33
		$overwrite = $overwrite !== null ? $overwrite : false;
34
		$content   = $this->__read(rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.($filename ?? '.env'));
35
36
		return $this
37
			->__load($content, $overwrite)
38
			->__check($required)
39
		;
40
	}
41
42
	/**
43
	 * @param string $var
44
	 *
45
	 * @return bool|mixed
46
	 */
47
	protected function __getEnv(string $var){
48
		$var   = strtoupper($var);
49
50
		return array_key_exists($var, $_ENV) ? $_ENV[$var] : false;
51
	}
52
53
	/**
54
	 * @param string $var
55
	 * @param string $value
56
	 *
57
	 * @return $this
58
	 */
59
	protected function __setEnv(string $var, string $value = null){
60
		$var   = strtoupper($var);
61
		$value = $this->__parse($value);
62
63
		// fill $_ENV explicitly, assuming variables_order="GPCS" (production)
64
		$_ENV[$var] = $value;
65
		putenv($var.'='.$value);
66
67
		return $this;
68
	}
69
70
	/**
71
	 * @param string $var
72
	 *
73
	 * @return $this
74
	 */
75
	protected function __unsetEnv(string $var){
76
		$var = strtoupper($var);
77
78
		unset($_ENV[$var]);
79
		putenv($var);
80
81
		return $this;
82
	}
83
84
	/**
85
	 * use with caution!
86
	 *
87
	 * @return $this
88
	 */
89
	protected function __clearEnv(){
90
		$_ENV = [];
91
92
		return $this;
93
	}
94
95
	/**
96
	 * @param string $file
97
	 *
98
	 * @return array
99
	 * @throws \chillerlan\Traits\TraitException
100
	 */
101
	private function __read(string $file):array{
102
103
		if(!is_readable($file) || !is_file($file)){
104
			throw new TraitException('invalid file: '.$file);
105
		}
106
107
		// Read file into an array of lines with auto-detected line endings
108
		$autodetect = ini_get('auto_detect_line_endings');
109
		ini_set('auto_detect_line_endings', '1');
110
		$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
111
		ini_set('auto_detect_line_endings', $autodetect);
112
113
		if(!is_array($lines) || empty($lines)){
114
			throw new TraitException('error while reading file: '.$file);
115
		}
116
117
		return array_map('trim', $lines);
118
	}
119
120
	/**
121
	 * @link http://php.net/variables-order
122
	 *
123
	 * @param array $data
124
	 * @param bool  $overwrite
125
	 *
126
	 * @return $this
127
	 */
128
	private function __load(array $data, bool $overwrite){
129
130
		foreach($data as $line){
131
132
			// skip empty lines and comments
133
			if(empty($line) || strpos($line, '#') === 0){
134
				continue;
135
			}
136
137
			$kv = array_map('trim', explode('=', $line, 2));
138
139
			// skip empty and numeric keys, keys with spaces, existing keys that shall not be overwritten
140
			if(empty($kv[0]) || is_numeric($kv[0]) || strpos($kv[0], ' ') !== false || (!$overwrite && array_key_exists($kv[0], $_ENV))){
141
				continue;
142
			}
143
144
			$this->__setEnv($kv[0], isset($kv[1]) ? trim($kv[1]) : null);
145
		}
146
147
		return $this;
148
	}
149
150
	/**
151
	 * @param string $value
152
	 *
153
	 * @return string|null
154
	 */
155
	private function __parse(string $value = null){
156
157
		if($value !== null){
158
159
			$q = $value[0] ?? null;
160
161
			$value = in_array($q, ["'", '"'], true)
162
				// handle quoted strings
163
				? preg_replace("/^$q((?:[^$q\\\\]|\\\\\\\\|\\\\$q)*)$q.*$/mx", '$1', $value)
164
				// skip inline comments
165
				: trim(explode('#', $value, 2)[0]);
166
167
			// handle multiline values
168
			$value = implode(PHP_EOL, explode('\\n', $value));
169
170
			// handle nested ${VARS}
171
			if(strpos($value, '$') !== false){
172
				$value = preg_replace_callback('/\${([_a-z\d]+)}/i', function($matches){
173
					return $this->__getEnv($matches[1]);
174
				}, $value);
175
			}
176
177
		}
178
179
		return $value;
180
	}
181
182
	/**
183
	 * @param array|null $required
184
	 *
185
	 * @return $this
186
	 * @throws \chillerlan\Traits\TraitException
187
	 */
188
	private function __check(array $required = null){
189
190
		if($required === null || empty($required)){
191
			return $this;
192
		}
193
194
		foreach($required as $var){
195
			if(!$this->__getEnv($var) || $this->__getEnv($var) === null){
196
				throw new TraitException('required variable not set: '.strtoupper($var));
197
			}
198
		}
199
200
		return $this;
201
	}
202
}
203