Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f448581

Browse files
committed
[Env] added the component
1 parent e66e6af commit f448581

13 files changed

+749
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"symfony/debug-bundle": "self.version",
4141
"symfony/doctrine-bridge": "self.version",
4242
"symfony/dom-crawler": "self.version",
43+
"symfony/env": "self.version",
4344
"symfony/event-dispatcher": "self.version",
4445
"symfony/expression-language": "self.version",
4546
"symfony/filesystem": "self.version",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
3.3.0
5+
-----
6+
7+
* added the component
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Dotenv;
13+
14+
use Symfony\Component\Dotenv\Exception\FormatException;
15+
use Symfony\Component\Dotenv\Exception\FormatExceptionContext;
16+
use Symfony\Component\Dotenv\Exception\PathException;
17+
use Symfony\Component\Process\Process;
18+
use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
19+
20+
/**
21+
* Manages .env files.
22+
*
23+
* @author Fabien Potencier <[email protected]>
24+
*/
25+
final class Dotenv
26+
{
27+
const VARNAME_REGEX = '[A-Z][A-Z0-9_]*';
28+
const STATE_VARNAME = 0;
29+
const STATE_VALUE = 1;
30+
31+
private $path;
32+
private $cursor;
33+
private $lineno;
34+
private $data;
35+
private $end;
36+
private $state;
37+
private $export;
38+
private $values;
39+
40+
/**
41+
* Loads one or several .env files.
42+
*
43+
* @param ...string A list of files to load
44+
*
45+
* @throws FormatException when a file has a syntax error
46+
* @throws PathException when a file does not exist or is not readable
47+
*/
48+
public function load(/*...$paths*/)
49+
{
50+
// func_get_args() to be replaced by a variadic argument for Symfony 4.0
51+
foreach (func_get_args() as $path) {
52+
if (!is_readable($path)) {
53+
throw new PathException($path);
54+
}
55+
56+
$this->populate($this->parse(file_get_contents($path), $path));
57+
}
58+
}
59+
60+
/**
61+
* Sets values as environment variables (via putenv, $_ENV, and $_SERVER).
62+
*
63+
* Note that existing environment variables are never overridden.
64+
*
65+
* @param array An array of env variables
66+
*/
67+
public function populate($values)
68+
{
69+
foreach ($values as $name => $value) {
70+
if (isset($_ENV[$name]) || false !== getenv($name)) {
71+
continue;
72+
}
73+
74+
putenv("$name=$value");
75+
$_ENV[$name] = $value;
76+
$_SERVER[$name] = $value;
77+
}
78+
}
79+
80+
/**
81+
* Parses the contents of an .env file.
82+
*
83+
* @param string $data The data to be parsed
84+
* @param string $path The original file name where data where stored (used for more meaningful error messages)
85+
*
86+
* @return array An array of env variables
87+
*
88+
* @throws FormatException when a file has a syntax error
89+
*/
90+
public function parse($data, $path = '.env')
91+
{
92+
$this->path = $path;
93+
$this->data = str_replace(array("\r\n", "\r"), "\n", $data);
94+
$this->lineno = 1;
95+
$this->cursor = 0;
96+
$this->end = strlen($this->data);
97+
$this->state = self::STATE_VARNAME;
98+
$this->values = array();
99+
$name = $value = '';
100+
101+
while ($this->cursor < $this->end) {
102+
switch ($this->state) {
103+
case self::STATE_VARNAME:
104+
$name = $this->lexVarname();
105+
$this->state = self::STATE_VALUE;
106+
break;
107+
108+
case self::STATE_VALUE:
109+
$this->values[$name] = $this->lexValue();
110+
$this->state = self::STATE_VARNAME;
111+
break;
112+
}
113+
}
114+
115+
if (self::STATE_VALUE === $this->state) {
116+
$this->values[$name] = '';
117+
}
118+
119+
return $this->values;
120+
}
121+
122+
private function lexVarname()
123+
{
124+
$this->skipEmptyLines();
125+
$this->skipWhitespace();
126+
127+
// optional export
128+
$this->export = false;
129+
if ($this->cursor === strpos($this->data, 'export ', $this->cursor)) {
130+
$this->export = true;
131+
$this->cursor += 7;
132+
$this->skipWhitespace();
133+
}
134+
135+
// var name
136+
if (!preg_match('/('.self::VARNAME_REGEX.'|\n)/Ai', $this->data, $matches, 0, $this->cursor)) {
137+
throw $this->createFormatException('Invalid character in variable name');
138+
}
139+
$this->moveCursor($matches[0]);
140+
141+
if ($this->cursor === $this->end) {
142+
if ($this->export) {
143+
throw $this->createFormatException('Unable to unset an environment variable');
144+
}
145+
146+
throw $this->createFormatException('Missing = in the environment variable declaration');
147+
}
148+
149+
if (' ' === $this->data[$this->cursor]) {
150+
throw $this->createFormatException('Whitespace are not supported after the variable name');
151+
}
152+
153+
if ('=' !== $this->data[$this->cursor]) {
154+
throw $this->createFormatException('Missing = in the environment variable declaration');
155+
}
156+
++$this->cursor;
157+
158+
return $matches[1];
159+
}
160+
161+
private function lexValue()
162+
{
163+
if ("\n" === $this->data[$this->cursor]) {
164+
$this->skipEmptyLines();
165+
166+
return '';
167+
}
168+
169+
if (preg_match('/ *(?!#.*)?(?:\n|$)/Am', $this->data, $matches, null, $this->cursor)) {
170+
$this->moveCursor($matches[0]);
171+
172+
return '';
173+
}
174+
175+
if (' ' === $this->data[$this->cursor]) {
176+
$this->skipComment();
177+
178+
// not a problem if the value is only a comment and/or whitespace
179+
if ($this->cursor === $this->end || "\n" === $this->data[$this->cursor - 1]) {
180+
return '';
181+
}
182+
183+
throw $this->createFormatException('Whitespace are not supported before the value');
184+
}
185+
186+
$value = '';
187+
$singleQuoted = false;
188+
$notQuoted = false;
189+
if ("'" === $this->data[$this->cursor]) {
190+
$singleQuoted = true;
191+
++$this->cursor;
192+
while ("\n" !== $this->data[$this->cursor]) {
193+
if ("'" === $this->data[$this->cursor]) {
194+
if ($this->cursor + 1 === $this->end) {
195+
break;
196+
}
197+
if ("'" !== $this->data[$this->cursor + 1]) {
198+
break;
199+
}
200+
201+
++$this->cursor;
202+
}
203+
$value .= $this->data[$this->cursor];
204+
++$this->cursor;
205+
206+
if ($this->cursor === $this->end) {
207+
throw $this->createFormatException('Missing quote to end the value');
208+
}
209+
}
210+
if ("\n" === $this->data[$this->cursor]) {
211+
throw $this->createFormatException('Missing quote to end the value');
212+
}
213+
++$this->cursor;
214+
$this->skipComment();
215+
} elseif ('"' === $this->data[$this->cursor]) {
216+
++$this->cursor;
217+
while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) {
218+
$value .= $this->data[$this->cursor];
219+
++$this->cursor;
220+
221+
if ($this->cursor === $this->end) {
222+
throw $this->createFormatException('Missing quote to end the value');
223+
}
224+
}
225+
if ("\n" === $this->data[$this->cursor]) {
226+
throw $this->createFormatException('Missing quote to end the value');
227+
}
228+
++$this->cursor;
229+
$this->skipComment();
230+
$value = str_replace(array('\\\\', '\\"', '\r', '\n'), array('\\', '"', "\r", "\n"), $value);
231+
} else {
232+
$notQuoted = true;
233+
while ($this->cursor < $this->end && "\n" !== $this->data[$this->cursor] && !(' ' === $this->data[$this->cursor - 1] && '#' === $this->data[$this->cursor])) {
234+
$value .= $this->data[$this->cursor];
235+
++$this->cursor;
236+
}
237+
$value = rtrim($value);
238+
$this->skipComment();
239+
}
240+
241+
$this->skipEmptyLines();
242+
243+
$currentValue = $value;
244+
if (!$singleQuoted) {
245+
$value = $this->resolveVariables($value);
246+
$value = $this->resolveCommands($value);
247+
}
248+
249+
if ($notQuoted && $currentValue == $value && preg_match('/\s+/', $value)) {
250+
throw $this->createFormatException('A value containing spaces must be surrounded by quotes');
251+
}
252+
253+
return $value;
254+
}
255+
256+
private function skipWhitespace()
257+
{
258+
$this->cursor += strspn($this->data, ' ', $this->cursor);
259+
}
260+
261+
private function skipEmptyLines()
262+
{
263+
if (preg_match('/(\n+|^#[^\n]*(\n*|$))+/Asm', $this->data, $match, null, $this->cursor)) {
264+
$this->moveCursor($match[0]);
265+
}
266+
}
267+
268+
private function skipComment()
269+
{
270+
if (preg_match('/ *#[^\n]*(\n*|$)/Asm', $this->data, $match, null, $this->cursor)) {
271+
$this->moveCursor($match[0]);
272+
}
273+
}
274+
275+
private function resolveCommands($value)
276+
{
277+
// commands are not supported on Windows
278+
if ('\\' === DIRECTORY_SEPARATOR) {
279+
return $value;
280+
}
281+
282+
if (false === strpos($value, '$')) {
283+
return $value;
284+
}
285+
286+
$regex = '/
287+
(\\\\)? # escaped with a backslash?
288+
\$
289+
(?<cmd>
290+
\( # require opening parenthesis
291+
([^()]|\g<cmd>)+ # allow any number of non-parens, or balanced parens (by nesting the <cmd> expression recursively)
292+
\) # require closing paren
293+
)
294+
/x';
295+
296+
return preg_replace_callback($regex, function ($matches) {
297+
if ('\\' === $matches[1]) {
298+
return substr($matches[0], 1);
299+
}
300+
301+
if (!class_exists(Process::class)) {
302+
throw new \LogicException('Resolving commands requires the Symfony Process component.');
303+
}
304+
305+
$process = new Process('echo '.$matches[0]);
306+
$process->inheritEnvironmentVariables(true);
307+
$process->setEnv($this->values);
308+
try {
309+
$process->mustRun();
310+
} catch (ProcessException $e) {
311+
throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput()));
312+
}
313+
314+
return preg_replace('/[\r\n]+$/', '', $process->getOutput());
315+
}, $value);
316+
}
317+
318+
private function resolveVariables($value)
319+
{
320+
if (false === strpos($value, '$')) {
321+
return $value;
322+
}
323+
324+
$regex = '/
325+
(\\\\)? # escaped with a backslash?
326+
\$
327+
(?!\() # no opening parenthesis
328+
(\{)? # optional brace
329+
('.self::VARNAME_REGEX.') # var name
330+
(\})? # optional closing brace
331+
/xi';
332+
333+
$value = preg_replace_callback($regex, function ($matches) {
334+
if ('\\' === $matches[1]) {
335+
return substr($matches[0], 1);
336+
}
337+
338+
if ('{' === $matches[2] && !isset($matches[4])) {
339+
throw $this->createFormatException('Unclosed braces on variable expansion');
340+
}
341+
342+
$value = (string) array_key_exists($matches[3], $this->values) ? $this->values[$matches[3]] : getenv($matches[3]);
343+
344+
if (!$matches[2] && isset($matches[4])) {
345+
$value .= '}';
346+
}
347+
348+
return $value;
349+
}, $value);
350+
351+
// unescape $
352+
return str_replace('\\$', '$', $value);
353+
}
354+
355+
private function moveCursor($text)
356+
{
357+
$this->cursor += strlen($text);
358+
$this->lineno += substr_count($text, "\n");
359+
}
360+
361+
private function createFormatException($message)
362+
{
363+
return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor));
364+
}
365+
}

0 commit comments

Comments
 (0)