Passed
Push — main ( ebe3be...5a020f )
by Dimitri
22:02 queued 12s
created

DotEnv::update()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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