diff --git a/UPGRADE-7.1.md b/UPGRADE-7.1.md index ac54aed0571bd..89113e16ac815 100644 --- a/UPGRADE-7.1.md +++ b/UPGRADE-7.1.md @@ -70,6 +70,7 @@ FrameworkBundle * Mark classes `ConfigBuilderCacheWarmer`, `Router`, `SerializerCacheWarmer`, `TranslationsCacheWarmer`, `Translator` and `ValidatorCacheWarmer` as `final` * Deprecate the `router.cache_dir` config option, the Router will always use the `kernel.build_dir` parameter + * Reset env vars when resetting the container Intl ---- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index df0d692ebdf7a..4b0475167c04b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * Attach the workflow's configuration to the `workflow` tag * Add the `allowed_recipients` option for mailer to allow some users to receive emails even if `recipients` is defined. + * Reset env vars when resetting the container 7.0 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php index a21d282702e13..8192f2f065c6f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; +use Symfony\Component\DependencyInjection\StaticEnvVarLoader; return static function (ContainerConfigurator $container) { $container->services() @@ -21,6 +22,9 @@ abstract_arg('Secret dir, set in FrameworkExtension'), service('secrets.decryption_key')->ignoreOnInvalid(), ]) + + ->set('secrets.env_var_loader', StaticEnvVarLoader::class) + ->args([service('secrets.vault')]) ->tag('container.env_var_loader') ->set('secrets.decryption_key') diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 0520a7add3791..54095a8d37ae5 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -14,6 +14,7 @@ CHANGELOG * [BC BREAK] When used in the `prependExtension()` method, the `ContainerConfigurator::import()` method now prepends the configuration instead of appending it * Cast env vars to null or bool when referencing them using `#[Autowire(env: '...')]` depending on the signature of the corresponding parameter * Add `#[AutowireInline]` attribute to allow service definition at the class level + * Add `StaticEnvVarLoader` 7.0 --- diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 4e37fe9e43573..b0c9710a70af8 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -287,6 +287,8 @@ public function reset(): void continue; } } + + $this->envCache = []; } /** diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index 57392807f75ab..20b1d25c8323d 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -14,15 +14,18 @@ use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Contracts\Service\ResetInterface; /** * @author Nicolas Grekas */ -class EnvVarProcessor implements EnvVarProcessorInterface +class EnvVarProcessor implements EnvVarProcessorInterface, ResetInterface { private ContainerInterface $container; /** @var \Traversable */ private \Traversable $loaders; + /** @var \Traversable */ + private \Traversable $originalLoaders; private array $loadedVars = []; /** @@ -31,7 +34,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface public function __construct(ContainerInterface $container, ?\Traversable $loaders = null) { $this->container = $container; - $this->loaders = $loaders ?? new \ArrayIterator(); + $this->originalLoaders = $this->loaders = $loaders ?? new \ArrayIterator(); } public static function getProvidedTypes(): array @@ -366,4 +369,10 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed throw new RuntimeException(sprintf('Unsupported env var prefix "%s" for env name "%s".', $prefix, $name)); } + + public function reset(): void + { + $this->loadedVars = []; + $this->loaders = $this->originalLoaders; + } } diff --git a/src/Symfony/Component/DependencyInjection/StaticEnvVarLoader.php b/src/Symfony/Component/DependencyInjection/StaticEnvVarLoader.php new file mode 100644 index 0000000000000..be1ada586e031 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/StaticEnvVarLoader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +class StaticEnvVarLoader implements EnvVarLoaderInterface +{ + private array $envVars; + + public function __construct(private EnvVarLoaderInterface $envVarLoader) + { + } + + public function loadEnvVars(): array + { + return $this->envVars ??= $this->envVarLoader->loadEnvVars(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index 54b036d80f053..cab51e4324d9d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -136,6 +136,68 @@ public function testGetEnvBool($value, $processed) $this->assertSame($processed, $result); } + public function testGetEnvCachesEnv() + { + $_ENV['FOO'] = ''; + + $GLOBALS['ENV_FOO'] = 'value'; + + $loaders = function () { + yield new class() implements EnvVarLoaderInterface { + public function loadEnvVars(): array + { + return ['FOO' => $GLOBALS['ENV_FOO']]; + } + }; + }; + + $processor = new EnvVarProcessor(new Container(), new RewindableGenerator($loaders, 1)); + + $noop = function () {}; + + $result = $processor->getEnv('string', 'FOO', $noop); + $this->assertSame('value', $result); + + $GLOBALS['ENV_FOO'] = 'new value'; + + $result = $processor->getEnv('string', 'FOO', $noop); + $this->assertSame('value', $result); + + unset($_ENV['FOO'], $GLOBALS['ENV_FOO']); + } + + public function testReset() + { + $_ENV['FOO'] = ''; + + $GLOBALS['ENV_FOO'] = 'value'; + + $loaders = function () { + yield new class() implements EnvVarLoaderInterface { + public function loadEnvVars(): array + { + return ['FOO' => $GLOBALS['ENV_FOO']]; + } + }; + }; + + $processor = new EnvVarProcessor(new Container(), new RewindableGenerator($loaders, 1)); + + $noop = function () {}; + + $result = $processor->getEnv('string', 'FOO', $noop); + $this->assertSame('value', $result); + + $GLOBALS['ENV_FOO'] = 'new value'; + + $processor->reset(); + + $result = $processor->getEnv('string', 'FOO', $noop); + $this->assertSame('new value', $result); + + unset($_ENV['FOO'], $GLOBALS['ENV_FOO']); + } + /** * @dataProvider validBools */ @@ -625,7 +687,7 @@ public static function validNullables() ['null', 'null'], ['Null', 'Null'], ['NULL', 'NULL'], - ]; + ]; } public function testRequireMissingFile() diff --git a/src/Symfony/Component/DependencyInjection/Tests/StaticEnvVarLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/StaticEnvVarLoaderTest.php new file mode 100644 index 0000000000000..8c63a2d234ace --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/StaticEnvVarLoaderTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; +use Symfony\Component\DependencyInjection\StaticEnvVarLoader; + +class StaticEnvVarLoaderTest extends TestCase +{ + public function testLoadEnvVarsCachesInnerLoaderEnvVars() + { + $innerLoader = new class(['FOO' => 'BAR']) implements EnvVarLoaderInterface { + /** @param array */ + public function __construct(public array $envVars = []) + { + } + + public function loadEnvVars(): array + { + return $this->envVars; + } + }; + + $loader = new StaticEnvVarLoader($innerLoader); + $this->assertSame(['FOO' => 'BAR'], $loader->loadEnvVars()); + + $innerLoader->envVars = ['BAR' => 'BAZ']; + $this->assertSame(['FOO' => 'BAR'], $loader->loadEnvVars()); + } +}