bug #35355 [DI] Fix EnvVar not loaded when Loader requires an env var (jderusse)

This PR was merged into the 4.4 branch.

Discussion
----------

[DI] Fix EnvVar not loaded when Loader requires an env var

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #35348
| License       | MIT
| Doc PR        | NA

When an EnvVarLoader has a dependency on an Env Var tried to be loaded (which is the case for SodiumVault that is configured with `default::SYMFONY_DECRYPTION_SECRET`) the Loader is not usable.

What happens:
- when trying to resolve `SYMFONY_DECRYPTION_SECRET`, the EnvVarProcessor iterates over loaders
- given SodiumVaultLoaders requires the same env variable `SYMFONY_DECRYPTION_SECRET`, it throws a `ParameterCircularReferenceException`
- letting the $loaders generator invalid

This PR, refactor the way loaders are iterated in order to rewind on failure.

Commits
-------

e119aa6c48 [DI] Fix EnvVar not loaded when Loader requires an env var
This commit is contained in:
Nicolas Grekas 2020-01-20 13:17:50 +01:00
commit e21b1538f3
2 changed files with 67 additions and 14 deletions

View File

@ -30,8 +30,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface
public function __construct(ContainerInterface $container, \Traversable $loaders = null)
{
$this->container = $container;
$this->loaders = new \IteratorIterator($loaders ?? new \ArrayIterator());
$this->loaders = $this->loaders->getInnerIterator();
$this->loaders = $loaders ?? new \ArrayIterator();
}
/**
@ -141,20 +140,32 @@ class EnvVarProcessor implements EnvVarProcessorInterface
}
}
$loaders = $this->loaders;
$this->loaders = new \ArrayIterator();
if (false === $env || null === $env) {
$loaders = $this->loaders;
$this->loaders = new \ArrayIterator();
try {
while ((false === $env || null === $env) && $loaders->valid()) {
$loader = $loaders->current();
$loaders->next();
$this->loadedVars[] = $vars = $loader->loadEnvVars();
$env = $vars[$name] ?? false;
try {
$i = 0;
$ended = true;
$count = $loaders instanceof \Countable ? $loaders->count() : 0;
foreach ($loaders as $loader) {
if (\count($this->loadedVars) > $i++) {
continue;
}
$this->loadedVars[] = $vars = $loader->loadEnvVars();
if (false !== $env = $vars[$name] ?? false) {
$ended = false;
break;
}
}
if ($ended || $count === $i) {
$loaders = $this->loaders;
}
} catch (ParameterCircularReferenceException $e) {
// skip loaders that need an env var that is not defined
} finally {
$this->loaders = $loaders;
}
} catch (ParameterCircularReferenceException $e) {
// skip loaders that need an env var that is not defined
} finally {
$this->loaders = $loaders;
}
if (false === $env || null === $env) {

View File

@ -3,10 +3,12 @@
namespace Symfony\Component\DependencyInjection\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
use Symfony\Component\DependencyInjection\EnvVarProcessor;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
class EnvVarProcessorTest extends TestCase
{
@ -553,4 +555,44 @@ CSV;
$result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {});
$this->assertSame('123', $result); // check twice
}
public function testCircularEnvLoader()
{
$container = new ContainerBuilder();
$container->setParameter('env(FOO_CONTAINER)', 'foo');
$container->compile();
$index = 0;
$loaders = function () use (&$index) {
if (0 === $index++) {
throw new ParameterCircularReferenceException(['FOO_CONTAINER']);
}
yield new class() implements EnvVarLoaderInterface {
public function loadEnvVars(): array
{
return [
'FOO_ENV_LOADER' => '123',
];
}
};
};
$processor = new EnvVarProcessor($container, new RewindableGenerator($loaders, 1));
$result = $processor->getEnv('string', 'FOO_CONTAINER', function () {});
$this->assertSame('foo', $result);
$result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {});
$this->assertSame('123', $result);
$result = $processor->getEnv('default', ':BAR_CONTAINER', function ($name) use ($processor) {
$this->assertSame('BAR_CONTAINER', $name);
return $processor->getEnv('string', $name, function () {});
});
$this->assertNull($result);
$this->assertSame(2, $index);
}
}