Passed
Push — 0.7.0 ( 86b65d...8b96b5 )
by Alexander
03:03
created

Dotenv::getResolverVariables()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 18
rs 9.9666
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\Store\StoreBuilder;
27
use Syscodes\Dotenv\Repository\RepositoryCreator;
28
29
/**
30
 * Manages .env files.
31
 * 
32
 * @author Alexander Campo <[email protected]>
33
 */
34
final class Dotenv
35
{
36
    /**
37
     * The loader instance.
38
     * 
39
     * @var \Syscodes\Dotenv\Loader\Loader $loader
40
     */
41
    protected $loader;
42
43
    /**
44
     * The Parser instance.
45
     * 
46
     * @var \Syscodes\Dotenv\Loader\Parser $parser
47
     */
48
    protected $parser;
49
50
    /**
51
     * The Repository creator instance.
52
     * 
53
     * @var \Syscodes\Dotenv\Repository\RepositoryCreator $repository
54
     */
55
    protected $repository;
56
57
    /**
58
     * The file store instance.
59
     * 
60
     * @var \Syscodes\Dotenv\Repository\FileStore $store
0 ignored issues
show
Bug introduced by
The type Syscodes\Dotenv\Repository\FileStore 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...
61
     */
62
    protected $store;
63
64
    /**
65
     * Activate use of putenv, by default is true.
66
     * 
67
     * @var bool $usePutenv 
68
     */
69
    protected $usePutenv = true;
70
71
    /**
72
     * Constructor. Create a new Dotenv instance.
73
     * 
74
     * @param  \Syscodes\Dotenv\Store\StoreBuilder  $store
75
     * @param  \Syscodes\Dotenv\Repository\RepositoryCreator  $repository
76
     * 
77
     * @return void
78
     */
79
    public function __construct($store, RepositoryCreator $repository)
80
    {
81
        $this->store      = $store;
0 ignored issues
show
Documentation Bug introduced by
It seems like $store of type Syscodes\Dotenv\Store\StoreBuilder is incompatible with the declared type Syscodes\Dotenv\Repository\FileStore of property $store.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
82
        $this->repository = $repository;
83
    }
84
85
    /**
86
     * Create a new Dotenv instance.
87
     * Builds the path to our file.
88
     * 
89
     * @param  \Syscodes\Dotenv\Repository\RepositoryCreator  $repository
90
     * @param  string|string[]  $path
91
     * @param  string|string[]  $names
92
     * @param  bool  $modeEnabled  (true by default)
93
     * 
94
     * @return \Syscodes\Dotenv\Dotenv
95
     */
96
    public static function create(RepositoryCreator $repository, $paths, $names = null, bool $modeEnabled = true)
97
    {
98
        $store = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames();
99
100
        foreach ((array) $paths as $path) {
101
            $store = $store->addPath($path);
102
        }
103
        
104
        foreach ((array) $names as $name) {
105
            $store = $store->addName($name);
106
        }
107
108
        if ($modeEnabled) {
109
            $store = $store->modeEnabled();
110
        }
111
112
        return new self($store->make(), $repository);
0 ignored issues
show
Bug introduced by
$store->make() of type Syscodes\Dotenv\Store\FileStore is incompatible with the type Syscodes\Dotenv\Store\StoreBuilder expected by parameter $store of Syscodes\Dotenv\Dotenv::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

112
        return new self(/** @scrutinizer ignore-type */ $store->make(), $repository);
Loading history...
113
    }
114
115
    /**
116
     * Determine if the line in the file is a comment.
117
     * 
118
     * @param  string  $line
119
     * 
120
     * @return bool
121
     */
122
    protected function isComment(string $line)
123
    {
124
        return strpos(ltrim($line), '#') === 0;
125
    }
126
127
    /**
128
     * Determine if the given line looks like it's setting a variable.
129
     * 
130
     * @param  string  $line
131
     * 
132
     * @return bool
133
     */
134
    protected function checkedLikeSetter(string $line)
135
    {
136
        return strpos($line, '=') !== false;
137
    }
138
139
    /**
140
     * Will load the .env file and process it. So that we end all settings in the PHP 
141
     * environment vars: getenv(), $_ENV, and $_SERVER.
142
     * 
143
     * @return bool
144
     */
145
    public function load()
146
    {
147
        foreach ($this->store->read() 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
                list($name, $value) = $this->normaliseEnvironmentVariable($line);
157
                $this->setVariable($name, $value);
0 ignored issues
show
Bug introduced by
$name of type array is incompatible with the type string expected by parameter $name of Syscodes\Dotenv\Dotenv::setVariable(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
                $this->setVariable(/** @scrutinizer ignore-type */ $name, $value);
Loading history...
158
            }
159
        }
160
161
        return true;
162
    }
163
164
    /**
165
     * Normalise the given environment variable.
166
     * 
167
     * @param  string  $name
168
     * @param  string  $value
169
     * 
170
     * @return array
171
     */
172
    public function normaliseEnvironmentVariable(string $name, string $value = '')
173
    {
174
        // Split our compound string into it's parts.
175
        if (strpos($name, '=') !== false) {
176
            list($name, $value) = explode('=', $name, 2);
177
        }
178
        
179
        $name  = trim($name);
180
        $value = trim($value);
181
        
182
        // Sanitize the name
183
        $name = $this->sanitizeName($name);
184
        
185
        // Sanitize the value
186
        $value = $this->sanitizeValue($value);
187
        
188
        // Get environment variables
189
        $value = $this->getResolverVariables($value);
0 ignored issues
show
Bug introduced by
$value of type array is incompatible with the type string expected by parameter $value of Syscodes\Dotenv\Dotenv::getResolverVariables(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

189
        $value = $this->getResolverVariables(/** @scrutinizer ignore-type */ $value);
Loading history...
190
        
191
        return [$name, $value];
192
    }
193
194
    /**
195
     * Strips quotes and the optional leading "export " from the environment variable name.
196
     * 
197
     * @param  string  $name
198
     * 
199
     * @return array
200
     */
201
    protected function sanitizeName(string $name)
202
    {
203
        return str_replace(array('export ', '\'', '"'), '', $name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return str_replace(array..., ''', '"'), '', $name) returns the type string which is incompatible with the documented return type array.
Loading history...
204
    }
205
206
    /**
207
     * Sets the variable into the environment. 
208
     * Will parse the string to look for {name}={value} pattern.
209
     * 
210
     * @param  string  $name
211
     * @param  string|null  $value
212
     * 
213
     * @return void
214
     */
215
    protected function setVariable(string $name, $value = null)
216
    {
217
        if ($this->usePutenv) {
218
            putenv("$name=$value");
219
        }   
220
        
221
        if (empty($_ENV[$name])) {
222
            $_ENV[$name] = $value;
223
        }
224
225
        if (empty($_SERVER[$name])) {
226
            $_SERVER[$name] = $value;
227
        }
228
    }
229
230
    /**
231
     * Strips quotes from the environment variable value.
232
     * 
233
     * This was borrowed from the excellent phpdotenv with very few changes.
234
     * https://github.com/vlucas/phpdotenv
235
     * 
236
     * @param  string  $value
237
     * 
238
     * @return array
239
     * 
240
     * @throws \InvalidArgumentException
241
     */
242
    protected function sanitizeValue($value)
243
    {
244
        if ( ! $value) {
245
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type string which is incompatible with the documented return type array.
Loading history...
246
        }
247
        
248
        // Does it begin with a quote?
249
        if (strpbrk($value[0], '"\'') !== false) {
250
            // value starts with a quote
251
            $quote = $value[0];
252
253
            $regexPattern = sprintf(
254
					'/^
255
					%1$s          # match a quote at the start of the value
256
					(             # capturing sub-pattern used
257
								  (?:          # we do not need to capture this
258
								   [^%1$s\\\\] # any character other than a quote or backslash
259
								   |\\\\\\\\   # or two backslashes together
260
								   |\\\\%1$s   # or an escaped quote e.g \"
261
								  )*           # as many characters that match the previous rules
262
					)             # end of the capturing sub-pattern
263
					%1$s          # and the closing quote
264
					.*$           # and discard any string after the closing quote
265
					/mx', $quote
266
            );
267
            
268
            $value = preg_replace($regexPattern, '$1', $value);
269
            $value = str_replace("\\$quote", $quote, $value);
270
            $value = str_replace('\\\\', '\\', $value);
271
        } else {
272
            $parts = explode(' #', $value, 2);
273
            $value = trim($parts[0]);
274
            // Unquoted values cannot contain whitespace
275
            if (preg_match('/\s+/', $value) > 0) {
276
                throw new InvalidArgumentException('.env values containing spaces must be surrounded by quotes.');
277
            }
278
        }
279
        
280
        return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type string which is incompatible with the documented return type array.
Loading history...
281
    }
282
    
283
    /**
284
     * Resolve the nested variables.
285
     * 
286
     * Look for ${varname} patterns in the variable value and replace with an existing
287
     * environment variable.
288
     * 
289
     * This was borrowed from the excellent phpdotenv with very few changes.
290
     * https://github.com/vlucas/phpdotenv
291
     * 
292
     * @param  string  $value
293
     * 
294
     * @return string
295
     */
296
    protected function getResolverVariables(string $value)
297
    {
298
        if (strpos($value, '$') !== false) {
299
            $loader = $this;
300
            $value = preg_replace_callback('~\${([a-zA-Z0-9_]+)}~', function ($matchedPatterns) use ($loader) {
301
                
302
                $nestedVariable = $loader->getVariable($matchedPatterns[1]);
303
304
                if (is_null($nestedVariable)) {
305
                    return $matchedPatterns[0];
306
                }
307
                
308
                return $nestedVariable;
309
                
310
            }, $value);
311
        }
312
        
313
        return $value;
314
    }
315
    
316
    /**
317
     * Search the different places for environment variables and return first value found.
318
     * This was borrowed from the excellent phpdotenv with very few changes.
319
     * https://github.com/vlucas/phpdotenv
320
     * 
321
     * @param  string  $name
322
     * 
323
     * @return string|null
324
     */
325
    protected function getVariable(string $name)
326
    {
327
        switch (true) {
328
            case array_key_exists($name, $_ENV):
329
                return $_ENV[$name];
330
                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...
331
            case array_key_exists($name, $_SERVER):
332
                return $_SERVER[$name];
333
                break;
334
            default:
335
                $value = getenv($name);
336
                // switch getenv default to null
337
                return $value === false ? null : $value;
338
        }
339
    }
340
}