Passed
Push — 0.7.0 ( 91f75a...a316e5 )
by Alexander
09:55
created

Dotenv::filePath()   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
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php 
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2021 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Dotenv;
24
25
use InvalidArgumentException;
26
use Syscodes\Dotenv\Resolve\Resolver;
27
28
/**
29
 * Manages .env files.
30
 * 
31
 * @author Alexander Campo <[email protected]>
32
 */
33
final class Dotenv
34
{
35
    protected const ALFANUMERIC_REGEX = '/\${([a-zA-Z0-9_]+)}/';
36
37
    /**
38
     * Get the file .env.
39
     * 
40
     * @var string $file 
41
     */
42
    protected $file;
43
44
    /**
45
     * The loader instance.
46
     * 
47
     * @var \Syscodes\Dotenv\Loader\Loader $loader
0 ignored issues
show
Bug introduced by
The type Syscodes\Dotenv\Loader\Loader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
48
     */
49
    protected $loader;
50
51
    /**
52
     * The directory where the .env file is located.
53
     * 
54
     * @var string $path  
55
     */
56
    protected $path;
57
58
    /**
59
     * Activate use of putenv, by default is true.
60
     * 
61
     * @var bool $usePutenv 
62
     */
63
    protected $usePutenv = true;
64
65
    /**
66
     * Constructor. Create a new Dotenv instance.
67
     * Builds the path to our file.
68
     * 
69
     * @param  string  $path
70
     * @param  string|null  $file  (null by default)
71
     * @param  bool  $usePutenv  (true by default)
72
     * 
73
     * @return void
74
     */
75
    public function __construct(string $path, string $file = null, bool $usePutenv = true)
76
    {
77
        $this->usePutenv = $usePutenv;
78
        $this->path      = $path;
79
        $this->file      = $file;
80
        //$this->loader    = $loader;
81
    }
82
83
    /**
84
     * Create a new Dotenv instance.
85
     * Builds the path to our file.
86
     * 
87
     * @param  string  $path
88
     * @param  string|null  $file  (null by default)
89
     * @param  bool  $usePutenv  (true by default)
90
     * 
91
     * @return \Syscodes\Dotenv\Dotenv
92
     */
93
    public static function create(string $path, string $file = null, bool $usePutenv = true)
94
    {
95
        return new self($path, $file, $usePutenv);
96
    }
97
98
    /**
99
     * Returns the full path to the file.
100
     * 
101
     * @return string
102
     */
103
    protected function filePath()
104
    {
105
        return Resolver::getFilePath($this->path, $this->file);
106
    }
107
108
    /**
109
     * Determine if the line in the file is a comment.
110
     * 
111
     * @param  string  $line
112
     * 
113
     * @return bool
114
     */
115
    protected function isComment(string $line)
116
    {
117
        return strpos(ltrim($line), '#') === 0;
118
    }
119
120
    /**
121
     * Determine if the given line looks like it's setting a variable.
122
     * 
123
     * @param  string  $line
124
     * 
125
     * @return bool
126
     */
127
    protected function checkedLikeSetter(string $line)
128
    {
129
        return strpos($line, '=') !== false;
130
    }
131
132
    /**
133
     * Will load the .env file and process it. So that we end all settings in the PHP 
134
     * environment vars: getenv(), $_ENV, and $_SERVER.
135
     * 
136
     * @return bool
137
     */
138
    public function load()
139
    {
140
        // Ensure file is readable
141
        if ( ! is_readable($this->filePath()) && ! is_file($this->filePath())) {
142
            throw new InvalidArgumentException(sprintf("The .env file is not readable: %s", $this->filePath()));
143
        }
144
        
145
        $lines = file($this->filePath(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
146
147
        foreach ($lines as $line) {
148
            // Is it a comment?
149
            if ($this->isComment($line)) {
150
                continue;
151
            }
152
153
            // If there is an equal sign, then we know we
154
			// are assigning a variable.
155
            if ($this->checkedLikeSetter($line)) {
156
                $this->setVariable($line);
157
            }
158
        }
159
160
        return true;
161
    }
162
163
    /**
164
     * Sets the variable into the environment. 
165
     * Will parse the string to look for {name}={value} pattern.
166
     * 
167
     * @param  string  $name
168
     * @param  string|null  $value
169
     * 
170
     * @return void
171
     */
172
    protected function setVariable(string $name, $value = null)
173
    {
174
        list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);
175
        
176
        $notHttpName = 0 !== strpos($name, 'HTTP_');
177
178
        if ($this->usePutenv) {
179
            putenv("$name=$value");
180
        }   
181
        
182
        if (empty($_ENV[$name])) {
183
            $_ENV[$name] = $value;
184
        }
185
186
        if ($notHttpName) {
187
            $_SERVER[$name] = $value;
188
        }
189
    }
190
191
    /**
192
     * Normalise the given environment variable.
193
     * 
194
     * @param  string  $name
195
     * @param  string  $value
196
     * 
197
     * @return array
198
     */
199
    public function normaliseEnvironmentVariable(string $name, $value)
200
    {
201
        // Split our compound string into it's parts.
202
        if (strpos($name, '=') !== false) {
203
            list($name, $value) = explode('=', $name, 2);
204
        }
205
        
206
        $name  = trim($name);
207
        $value = trim($value);
208
        
209
        // Sanitize the name
210
        list($name, $value) = $this->sanitizeName($name, $value);
211
        
212
        // Sanitize the value
213
        list($name, $value) = $this->sanitizeValue($name, $value);
214
        
215
        $value = $this->resolveNestedVariables($value);
216
        
217
        return [$name, $value];
218
    }
219
220
    /**
221
     * Strips quotes and the optional leading "export " from the environment variable name.
222
     * 
223
     * @param  string  $name
224
     * @param  string  $value
225
     * 
226
     * @return array
227
     */
228
    protected function sanitizeName(string $name, $value)
229
    {
230
        $name = str_replace(array('export ', '\'', '"'), '', $name);
231
232
        return [$name, $value];
233
    }
234
235
    /**
236
     * Strips quotes from the environment variable value.
237
     * 
238
     * This was borrowed from the excellent phpdotenv with very few changes.
239
     * https://github.com/vlucas/phpdotenv
240
     * 
241
     * @param  string  $name
242
     * @param  string  $value
243
     * 
244
     * @return array
245
     * 
246
     * @throws \InvalidArgumentException
247
     */
248
    protected function sanitizeValue(string $name, $value)
249
    {
250
        if ( ! $value) {
251
            return [$name, $value];
252
        }
253
        
254
        // Does it begin with a quote?
255
        if (strpbrk($value[0], '"\'') !== false) {
256
            // value starts with a quote
257
            $quote = $value[0];
258
259
            $regexPattern = sprintf(
260
					'/^
261
					%1$s          # match a quote at the start of the value
262
					(             # capturing sub-pattern used
263
								  (?:          # we do not need to capture this
264
								   [^%1$s\\\\] # any character other than a quote or backslash
265
								   |\\\\\\\\   # or two backslashes together
266
								   |\\\\%1$s   # or an escaped quote e.g \"
267
								  )*           # as many characters that match the previous rules
268
					)             # end of the capturing sub-pattern
269
					%1$s          # and the closing quote
270
					.*$           # and discard any string after the closing quote
271
					/mx', $quote
272
            );
273
            
274
            $value = preg_replace($regexPattern, '$1', $value);
275
            $value = str_replace("\\$quote", $quote, $value);
276
            $value = str_replace('\\\\', '\\', $value);
277
        } else {
278
            $parts = explode(' #', $value, 2);
279
            $value = trim($parts[0]);
280
            // Unquoted values cannot contain whitespace
281
            if (preg_match('/\s+/', $value) > 0) {
282
                throw new InvalidArgumentException('.env values containing spaces must be surrounded by quotes.');
283
            }
284
        }
285
        
286
        return [$name, $value];
287
    }
288
    
289
    /**
290
     * Resolve the nested variables.
291
     * 
292
     * Look for ${varname} patterns in the variable value and replace with an existing
293
     * environment variable.
294
     * 
295
     * This was borrowed from the excellent phpdotenv with very few changes.
296
     * https://github.com/vlucas/phpdotenv
297
     * 
298
     * @param  string  $value
299
     * 
300
     * @return string
301
     */
302
    protected function resolveNestedVariables(string $value)
303
    {
304
        if (strpos($value, '$') !== false) {
305
            $loader = $this;
306
            $value = preg_replace_callback(self::ALFANUMERIC_REGEX, function ($matchedPatterns) use ($loader) {
307
                
308
                $nestedVariable = $loader->getVariable($matchedPatterns[1]);
309
310
                if (is_null($nestedVariable)) {
311
                    return $matchedPatterns[0];
312
                }
313
                
314
                return $nestedVariable;
315
                
316
            }, $value);
317
        }
318
        
319
        return $value;
320
    }
321
    
322
    /**
323
     * Search the different places for environment variables and return first value found.
324
     * This was borrowed from the excellent phpdotenv with very few changes.
325
     * https://github.com/vlucas/phpdoten
326
     * 
327
     * @param  string  $name
328
     * 
329
     * @return string|null
330
     */
331
    protected function getVariable(string $name)
332
    {
333
        switch (true) {
334
            case array_key_exists($name, $_ENV):
335
                return $_ENV[$name];
336
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
337
            case array_key_exists($name, $_SERVER):
338
                return $_SERVER[$name];
339
                break;
340
            default:
341
                $value = getenv($name);
342
                // switch getenv default to null
343
                return $value === false ? null : $value;
344
        }
345
    }
346
}