Passed
Push — main ( c6deb1...79ccdf )
by Dimitri
12:23
created

DotEnv::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Loader;
13
14
use BlitzPHP\Traits\SingletonTrait;
15
use InvalidArgumentException;
16
17
/**
18
 * Environment-specific configuration
19
 */
20
class DotEnv
21
{
22
    use SingletonTrait;
23
24
    /**
25
     * Le répertoire où se trouve le fichier .env.
26
     *
27
     * @var string
28
     */
29
    protected $path;
30
31
    /**
32
     * Construit le chemin vers notre fichier.
33
     */
34
    private function __construct(string $path, string $file = '.env')
35
    {
36
        $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
37
    }
38
39
    public static function init(string $path, string $file = '.env')
40
    {
41
        return self::instance($path, $file)->load();
42
    }
43
44
    /**
45
     * Le point d'entrée principal chargera le fichier .env et le traitera
46
     * pour que nous nous retrouvions avec tous les paramètres dans l'environnement PHP vars
47
     * (c'est-à-dire getenv(), $_ENV et $_SERVER)
48
     */
49
    public function load(): bool
50
    {
51
        $vars = $this->parse();
52
53
        if ($vars === null) {
54
            return false;
55
        }
56
57
        foreach ($vars as $name => $value) {
58
            $this->setVariable($name, $value);
59
        }
60
61
        return true; // notifie de la reussite de l'operation
62
    }
63
64
	/**
65
     * Remplace les valeurs dans le fichiers .env
66
	 * 
67
	 * Si une valeur n'existe pas, elle est ajoutée au fichier
68
     */
69
	public function replace(array $data, bool $reload = true): bool
70
	{	
71
		$oldFileContents = (string) file_get_contents($this->path);
72
73
		foreach ($data as $key => $value) {
74
			$replacementKey  = "\n{$key} = {$value}";
75
			if (strpos($oldFileContents, $key) === false) {
76
				if (file_put_contents($this->path, $replacementKey, FILE_APPEND) === false) {
77
					return false;
78
				}
79
				unset($data[$key]);
80
			}
81
		}
82
83
		if ($data === []) {
84
			if ($reload) {
85
				return $this->load();
86
			}
87
			return true;
88
		}
89
90
		return $this->update($data, $reload);
91
	}
92
93
    /**
94
     * Modifie les valeurs dans le fichiers .env
95
     */
96
    public function update(array $data = [], bool $reload = true): bool
97
    {
98
        foreach ($data as $key => $value) {
99
            if (env($key) === $value) {
100
                unset($data[$key]);
101
            }
102
        }
103
104
        if (! count($data)) {
105
            return false;
106
        }
107
108
        // ecrit seulement si il y'a des changements dans le contenu
109
110
        $env = file_get_contents($this->path);
111
        $env = explode("\n", $env);
112
113
        foreach ((array) $data as $key => $value) {
114
            foreach ($env as $env_key => $env_value) {
115
                $entry = explode('=', $env_value, 2);
116
                $entry = array_map('trim', $entry);
117
                if ($entry[0] === $key) {
118
                    $env[$env_key] = $key . '=' . (is_string($value) ? '"' . $value . '"' : $value);
119
                } else {
120
                    $env[$env_key] = $env_value;
121
                }
122
            }
123
        }
124
125
        $env = implode("\n", $env);
126
        file_put_contents($this->path, $env);
127
128
        if ($reload) {
129
            return $this->load();
130
        }
131
132
        return true;
133
    }
134
135
    /**
136
     * Parse le fichier .env file dans un tableau de cle => valeur
137
     */
138
    public function parse(): ?array
139
    {
140
        // Nous ne voulons pas imposer la présence d'un fichier .env, ils devraient être facultatifs.
141
        if (! is_file($this->path)) {
142
            return null;
143
        }
144
145
        // Assurez-vous que le fichier est lisible
146
        if (! is_readable($this->path)) {
147
            throw new InvalidArgumentException("The .env file is not readable: {$this->path}");
148
        }
149
150
        $vars = [];
151
152
        $lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
153
154
        foreach ($lines as $line) {
155
            // C'est un commentaire?
156
            if (strpos(trim($line), '#') === 0) {
157
                continue;
158
            }
159
160
            // S'il y a un signe égal, alors nous savons que nous affectons une variable.
161
            if (strpos($line, '=') !== false) {
162
                [$name, $value] = $this->normaliseVariable($line);
163
                $vars[$name]    = $value;
164
            }
165
        }
166
167
        return $vars;
168
    }
169
170
    /**
171
     * Définit la variable dans l'environnement. Analysera la chaîne
172
     * premier à rechercher le modèle {name}={value}, assurez-vous que imbriqué
173
     * les variables sont gérées et débarrassées des guillemets simples et doubles.
174
     */
175
    protected function setVariable(string $name, string $value = '')
176
    {
177
        if (! getenv($name, true)) {
178
            putenv("{$name}={$value}");
179
        }
180
        if (empty($_ENV[$name])) {
181
            $_ENV[$name] = $value;
182
        }
183
        if (empty($_SERVER[$name])) {
184
            $_SERVER[$name] = $value;
185
        }
186
    }
187
188
    /**
189
     * Analyse l'affectation, nettoie le $name et la $value, et s'assure
190
     * que les variables imbriquées sont gérées.
191
     */
192
    public function normaliseVariable(string $name, string $value = ''): array
193
    {
194
        // Divisez notre chaîne composée en ses parties.
195
        if (strpos($name, '=') !== false) {
196
            [$name, $value] = explode('=', $name, 2);
197
        }
198
199
        $name  = trim($name);
200
        $value = trim($value);
201
202
        // Assainir le nom
203
        $name = str_replace(['export', '\'', '"'], '', $name);
204
205
        // Assainir la valeur
206
        $value = $this->sanitizeValue($value);
207
208
        $value = $this->resolveNestedVariables($value);
209
210
        return [
211
            $name,
212
            $value,
213
        ];
214
    }
215
216
    /**
217
     * Supprime les guillemets de la valeur de la variable d'environnement.
218
     *
219
     * Ceci a été emprunté à l'excellent phpdotenv avec très peu de modifications.
220
     * https://github.com/vlucas/phpdotenv
221
     *
222
     * @throws InvalidArgumentException
223
     */
224
    protected function sanitizeValue(string $value): string
225
    {
226
        if (! $value) {
227
            return $value;
228
        }
229
230
        // Commence-t-il par une citation ?
231
        if (strpbrk($value[0], '"\'') !== false) {
232
            // la valeur commence par un guillemet
233
            $quote        = $value[0];
234
            $regexPattern = sprintf(
235
                '/^
236
					%1$s          # match a quote at the start of the value
237
					(             # capturing sub-pattern used
238
								  (?:          # we do not need to capture this
239
								   [^%1$s\\\\] # any character other than a quote or backslash
240
								   |\\\\\\\\   # or two backslashes together
241
								   |\\\\%1$s   # or an escaped quote e.g \"
242
								  )*           # as many characters that match the previous rules
243
					)             # end of the capturing sub-pattern
244
					%1$s          # and the closing quote
245
					.*$           # and discard any string after the closing quote
246
					/mx',
247
                $quote
248
            );
249
            $value = preg_replace($regexPattern, '$1', $value);
250
            $value = str_replace("\\{$quote}", $quote, $value);
251
            $value = str_replace('\\\\', '\\', $value);
252
        } else {
253
            $parts = explode(' #', $value, 2);
254
255
            $value = trim($parts[0]);
256
257
            // Les valeurs sans guillemets ne peuvent pas contenir d'espaces
258
            if (preg_match('/\s+/', $value) > 0) {
259
                throw new InvalidArgumentException('.env values containing spaces must be surrounded by quotes.');
260
            }
261
        }
262
263
        return $value;
264
    }
265
266
    /**
267
     * Résolvez les variables imbriquées.
268
     *
269
     * Recherchez les modèles ${varname} dans la valeur de la variable et remplacez-les par un existant
270
     * variables d'environnement.
271
     *
272
     * Ceci a été emprunté à l'excellent phpdotenv avec très peu de modifications.
273
     * https://github.com/vlucas/phpdotenv
274
     */
275
    protected function resolveNestedVariables(string $value): string
276
    {
277
        if (strpos($value, '$') !== false) {
278
            $loader = $this;
279
280
            $value = preg_replace_callback(
281
                '/\${([a-zA-Z0-9_]+)}/',
282
                static function ($matchedPatterns) use ($loader) {
283
                    $nestedVariable = $loader->getVariable($matchedPatterns[1]);
284
285
                    if (null === $nestedVariable) {
286
                        return $matchedPatterns[0];
287
                    }
288
289
                    return $nestedVariable;
290
                },
291
                $value
292
            );
293
        }
294
295
        return $value;
296
    }
297
298
    /**
299
     * Rechercher les différents endroits pour les variables d'environnement et renvoyer la première valeur trouvée.
300
     *
301
     * Ceci a été emprunté à l'excellent phpdotenv avec très peu de modifications.
302
     * https://github.com/vlucas/phpdotenv
303
     */
304
    protected function getVariable(string $name): ?string
305
    {
306
        switch (true) {
307
            case array_key_exists($name, $_ENV):
308
                return $_ENV[$name];
309
310
            case array_key_exists($name, $_SERVER):
311
                return $_SERVER[$name];
312
313
            default:
314
                $value = getenv($name);
315
316
                // passe la valeur par défaut de getenv à null
317
                return $value === false ? null : $value;
318
        }
319
    }
320
}
321