Skip to content

Commit 8b337fc

Browse files
committed
feature #35308 [Dotenv] Add Dotenv::bootEnv() to check for .env.local.php before calling Dotenv::loadEnv() (nicolas-grekas)
This PR was merged into the 5.1-dev branch. Discussion ---------- [Dotenv] Add Dotenv::bootEnv() to check for .env.local.php before calling Dotenv::loadEnv() | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | yes | Tickets | - | License | MIT | Doc PR | - The goal of this PR is to eventually get rid of the `config/bootstrap.php` file in Symfony 5.1 apps. I think we've done enough iterations on that piece of bootstrapping logic to put it inside the `Dotenv` component. This fully replaces https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/config/bootstrap.php It doesn't conflict with current apps so they'll be fine keeping the `config/bootstrap.php` file until they're upgraded. The new bootstrapping logic will require adding this line in `bin/console` and `public/index.php`: ```php (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); ``` Recipes updated at symfony/recipes#724 Commits ------- 98c7d30 [Dotenv] Add Dotenv::bootEnv() to check for .env.local.php before calling Dotenv::loadEnv()
2 parents 7ecb5aa + 98c7d30 commit 8b337fc

File tree

8 files changed

+141
-40
lines changed

8 files changed

+141
-40
lines changed

UPGRADE-5.1.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Console
66

77
* `Command::setHidden()` is final since Symfony 5.1
88

9+
Dotenv
10+
------
11+
12+
* Deprecated passing `$usePutenv` argument to Dotenv's constructor, use `Dotenv::usePutenv()` instead.
13+
914
EventDispatcher
1015
---------------
1116

UPGRADE-6.0.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Console
66

77
* `Command::setHidden()` has a default value (`true`) for `$hidden` parameter
88

9+
Dotenv
10+
------
11+
12+
* Removed argument `$usePutenv` from Dotenv's constructor, use `Dotenv::usePutenv()` instead.
13+
914
EventDispatcher
1015
---------------
1116

src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/DotenvVaultTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function testEncryptAndDecrypt()
3838
$vault->seal('foo', $plain);
3939

4040
unset($_SERVER['foo'], $_ENV['foo']);
41-
(new Dotenv(false))->load($this->envFile);
41+
(new Dotenv())->load($this->envFile);
4242

4343
$decrypted = $vault->reveal('foo');
4444
$this->assertSame($plain, $decrypted);
@@ -50,7 +50,7 @@ public function testEncryptAndDecrypt()
5050
$this->assertFalse($vault->remove('foo'));
5151

5252
unset($_SERVER['foo'], $_ENV['foo']);
53-
(new Dotenv(false))->load($this->envFile);
53+
(new Dotenv())->load($this->envFile);
5454

5555
$this->assertArrayNotHasKey('foo', $vault->list());
5656
}

src/Symfony/Bundle/FrameworkBundle/composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"symfony/console": "^4.4|^5.0",
3838
"symfony/css-selector": "^4.4|^5.0",
3939
"symfony/dom-crawler": "^4.4|^5.0",
40-
"symfony/dotenv": "^4.4|^5.0",
40+
"symfony/dotenv": "^5.1",
4141
"symfony/polyfill-intl-icu": "~1.0",
4242
"symfony/form": "^4.4|^5.0",
4343
"symfony/expression-language": "^4.4|^5.0",
@@ -71,7 +71,7 @@
7171
"symfony/asset": "<4.4",
7272
"symfony/browser-kit": "<4.4",
7373
"symfony/console": "<4.4",
74-
"symfony/dotenv": "<4.4",
74+
"symfony/dotenv": "<5.1",
7575
"symfony/dom-crawler": "<4.4",
7676
"symfony/http-client": "<4.4",
7777
"symfony/form": "<4.4",

src/Symfony/Component/Console/composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
},
4242
"conflict": {
4343
"symfony/dependency-injection": "<4.4",
44+
"symfony/dotenv": "<5.1",
4445
"symfony/event-dispatcher": "<4.4",
4546
"symfony/lock": "<4.4",
4647
"symfony/process": "<4.4"

src/Symfony/Component/Dotenv/CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
CHANGELOG
22
=========
33

4+
5.1.0
5+
-----
6+
7+
* added `Dotenv::bootEnv()` to check for `.env.local.php` before calling `Dotenv::loadEnv()`
8+
* added `Dotenv::setProdEnvs()` and `Dotenv::usePutenv()`
9+
* made Dotenv's constructor accept `$envKey` and `$debugKey` arguments, to define
10+
the name of the env vars that configure the env name and debug settings
11+
* deprecated passing `$usePutenv` argument to Dotenv's constructor
12+
413
5.0.0
514
-----
615

src/Symfony/Component/Dotenv/Dotenv.php

+72-12
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,47 @@ final class Dotenv
3535
private $data;
3636
private $end;
3737
private $values;
38-
private $usePutenv;
38+
private $envKey;
39+
private $debugKey;
40+
private $prodEnvs = ['prod'];
41+
private $usePutenv = false;
3942

4043
/**
41-
* @var bool If `putenv()` should be used to define environment variables or not.
42-
* Beware that `putenv()` is not thread safe, that's why this setting defaults to false
44+
* @param string $envKey
4345
*/
44-
public function __construct(bool $usePutenv = false)
46+
public function __construct($envKey = 'APP_ENV', string $debugKey = 'APP_DEBUG')
47+
{
48+
if (\in_array($envKey = (string) $envKey, ['1', ''], true)) {
49+
@trigger_error(sprintf('Passing a boolean to the constructor of "%s" is deprecated since Symfony 5.1, use "Dotenv::usePutenv()".', __CLASS__), E_USER_DEPRECATED);
50+
$this->usePutenv = (bool) $envKey;
51+
$envKey = 'APP_ENV';
52+
}
53+
54+
$this->envKey = $envKey;
55+
$this->debugKey = $debugKey;
56+
}
57+
58+
/**
59+
* @return $this
60+
*/
61+
public function setProdEnvs(array $prodEnvs): self
62+
{
63+
$this->prodEnvs = $prodEnvs;
64+
65+
return $this;
66+
}
67+
68+
/**
69+
* @param bool $usePutenv If `putenv()` should be used to define environment variables or not.
70+
* Beware that `putenv()` is not thread safe, that's why this setting defaults to false
71+
*
72+
* @return $this
73+
*/
74+
public function usePutenv($usePutenv = true): self
4575
{
4676
$this->usePutenv = $usePutenv;
77+
78+
return $this;
4779
}
4880

4981
/**
@@ -66,29 +98,31 @@ public function load(string $path, string ...$extraPaths): void
6698
* .env.local is always ignored in test env because tests should produce the same results for everyone.
6799
* .env.dist is loaded when it exists and .env is not found.
68100
*
69-
* @param string $path A file to load
70-
* @param string $varName The name of the env vars that defines the app env
71-
* @param string $defaultEnv The app env to use when none is defined
72-
* @param array $testEnvs A list of app envs for which .env.local should be ignored
101+
* @param string $path A file to load
102+
* @param string $envKey|null The name of the env vars that defines the app env
103+
* @param string $defaultEnv The app env to use when none is defined
104+
* @param array $testEnvs A list of app envs for which .env.local should be ignored
73105
*
74106
* @throws FormatException when a file has a syntax error
75107
* @throws PathException when a file does not exist or is not readable
76108
*/
77-
public function loadEnv(string $path, string $varName = 'APP_ENV', string $defaultEnv = 'dev', array $testEnvs = ['test']): void
109+
public function loadEnv(string $path, string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test']): void
78110
{
111+
$k = $envKey ?? $this->envKey;
112+
79113
if (file_exists($path) || !file_exists($p = "$path.dist")) {
80114
$this->load($path);
81115
} else {
82116
$this->load($p);
83117
}
84118

85-
if (null === $env = $_SERVER[$varName] ?? $_ENV[$varName] ?? null) {
86-
$this->populate([$varName => $env = $defaultEnv]);
119+
if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) {
120+
$this->populate([$k => $env = $defaultEnv]);
87121
}
88122

89123
if (!\in_array($env, $testEnvs, true) && file_exists($p = "$path.local")) {
90124
$this->load($p);
91-
$env = $_SERVER[$varName] ?? $_ENV[$varName] ?? $env;
125+
$env = $_SERVER[$k] ?? $_ENV[$k] ?? $env;
92126
}
93127

94128
if ('local' === $env) {
@@ -104,6 +138,32 @@ public function loadEnv(string $path, string $varName = 'APP_ENV', string $defau
104138
}
105139
}
106140

141+
/**
142+
* Loads env vars from .env.local.php if the file exists or from the other .env files otherwise.
143+
*
144+
* This method also configures the APP_DEBUG env var according to the current APP_ENV.
145+
*
146+
* See method loadEnv() for rules related to .env files.
147+
*/
148+
public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnvs = ['test']): void
149+
{
150+
$p = $path.'.local.php';
151+
$env = (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($p)) || file_exists($p) ? include $p : null;
152+
$k = $this->envKey;
153+
154+
if (\is_array($env) && (!isset($env[$k]) || ($_SERVER[$k] ?? $_ENV[$k] ?? $env[$k]) === $env[$k])) {
155+
$this->populate($env);
156+
} else {
157+
$this->loadEnv($path, $k, $defaultEnv, $testEnvs);
158+
}
159+
160+
$_SERVER += $_ENV;
161+
162+
$k = $this->debugKey;
163+
$debug = $_SERVER[$k] ?? !\in_array($_SERVER[$this->envKey], $this->prodEnvs, true);
164+
$_SERVER[$k] = $_ENV[$k] = (int) $debug || (!\is_bool($debug) && filter_var($debug, FILTER_VALIDATE_BOOLEAN)) ? '1' : '0';
165+
}
166+
107167
/**
108168
* Loads one or several .env files and enables override existing vars.
109169
*

0 commit comments

Comments
 (0)