- * @author Nicolas Grekas
- *
- * @internal
- */
-class SecretEnvVarProcessor implements EnvVarProcessorInterface
-{
- private $vault;
- private $localVault;
-
- public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
- {
- $this->vault = $vault;
- $this->localVault = $localVault;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function getProvidedTypes()
- {
- return [
- 'secret' => 'string',
- ];
- }
-
- /**
- * {@inheritdoc}
- */
- public function getEnv($prefix, $name, \Closure $getEnv): string
- {
- if (null !== $this->localVault && null !== ($secret = $this->localVault->reveal($name)) && \array_key_exists($name, $this->vault->list())) {
- return $secret;
- }
-
- if (null !== $secret = $this->vault->reveal($name)) {
- return $secret;
- }
-
- throw new EnvNotFoundException($this->vault->getLastMessage() ?? sprintf('Secret "%s" not found or decryption key is missing.', $name));
- }
-}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php
index cb6e9f527f..e6fcab5060 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php
@@ -11,6 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle\Secrets;
+use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
+
/**
* @author Tobias Schultze
* @author Jérémy Derussé
@@ -18,7 +20,7 @@ namespace Symfony\Bundle\FrameworkBundle\Secrets;
*
* @internal
*/
-class SodiumVault extends AbstractVault
+class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
{
private $encryptionKey;
private $decryptionKey;
@@ -56,8 +58,8 @@ class SodiumVault extends AbstractVault
// ignore failures to load keys
}
- if ('' !== $this->decryptionKey && !file_exists($this->pathPrefix.'sodium.encrypt.public')) {
- $this->export('sodium.encrypt.public', $this->encryptionKey);
+ if ('' !== $this->decryptionKey && !file_exists($this->pathPrefix.'encrypt.public.php')) {
+ $this->export('encrypt.public', $this->encryptionKey);
}
if (!$override && null !== $this->encryptionKey) {
@@ -69,10 +71,10 @@ class SodiumVault extends AbstractVault
$this->decryptionKey = sodium_crypto_box_keypair();
$this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey);
- $this->export('sodium.encrypt.public', $this->encryptionKey);
- $this->export('sodium.decrypt.private', $this->decryptionKey);
+ $this->export('encrypt.public', $this->encryptionKey);
+ $this->export('decrypt.private', $this->decryptionKey);
- $this->lastMessage = sprintf('Sodium keys have been generated at "%s*.{public,private}".', $this->getPrettyPath($this->pathPrefix));
+ $this->lastMessage = sprintf('Sodium keys have been generated at "%s*.public/private.php".', $this->getPrettyPath($this->pathPrefix));
return true;
}
@@ -82,12 +84,12 @@ class SodiumVault extends AbstractVault
$this->lastMessage = null;
$this->validateName($name);
$this->loadKeys();
- $this->export($name.'.'.substr_replace(md5($name), '.sodium', -26), sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey)));
+ $this->export($name.'.'.substr(md5($name), 0, 6), sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey)));
$list = $this->list();
$list[$name] = null;
uksort($list, 'strnatcmp');
- file_put_contents($this->pathPrefix.'sodium.list', sprintf("pathPrefix.'list.php', sprintf("lastMessage = sprintf('Secret "%s" encrypted in "%s"; you can commit it.', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
}
@@ -97,7 +99,7 @@ class SodiumVault extends AbstractVault
$this->lastMessage = null;
$this->validateName($name);
- if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.sodium', -26))) {
+ if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.php', -26))) {
$this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
return null;
@@ -131,7 +133,7 @@ class SodiumVault extends AbstractVault
$this->lastMessage = null;
$this->validateName($name);
- if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.sodium', -26))) {
+ if (!file_exists($file = $this->pathPrefix.$name.'.'.substr_replace(md5($name), '.php', -26))) {
$this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
return false;
@@ -139,7 +141,7 @@ class SodiumVault extends AbstractVault
$list = $this->list();
unset($list[$name]);
- file_put_contents($this->pathPrefix.'sodium.list', sprintf("pathPrefix.'list.php', sprintf("lastMessage = sprintf('Secret "%s" removed from "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR));
@@ -150,7 +152,7 @@ class SodiumVault extends AbstractVault
{
$this->lastMessage = null;
- if (!file_exists($file = $this->pathPrefix.'sodium.list')) {
+ if (!file_exists($file = $this->pathPrefix.'list.php')) {
return [];
}
@@ -167,6 +169,11 @@ class SodiumVault extends AbstractVault
return $secrets;
}
+ public function loadEnvVars(): array
+ {
+ return $this->list(true);
+ }
+
private function loadKeys(): void
{
if (!\function_exists('sodium_crypto_box_seal')) {
@@ -177,12 +184,12 @@ class SodiumVault extends AbstractVault
return;
}
- if (file_exists($this->pathPrefix.'sodium.decrypt.private')) {
- $this->decryptionKey = (string) include $this->pathPrefix.'sodium.decrypt.private';
+ if (file_exists($this->pathPrefix.'decrypt.private.php')) {
+ $this->decryptionKey = (string) include $this->pathPrefix.'decrypt.private.php';
}
- if (file_exists($this->pathPrefix.'sodium.encrypt.public')) {
- $this->encryptionKey = (string) include $this->pathPrefix.'sodium.encrypt.public';
+ if (file_exists($this->pathPrefix.'encrypt.public.php')) {
+ $this->encryptionKey = (string) include $this->pathPrefix.'encrypt.public.php';
} elseif ('' !== $this->decryptionKey) {
$this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey);
} else {
@@ -196,7 +203,7 @@ class SodiumVault extends AbstractVault
$data = str_replace('%', '\x', rawurlencode($data));
$data = sprintf("pathPrefix.$file, $data, LOCK_EX)) {
+ if (false === file_put_contents($this->pathPrefix.$file.'.php', $data, LOCK_EX)) {
$e = error_get_last();
throw new \ErrorException($e['message'] ?? 'Failed to write secrets data.', 0, $e['type'] ?? E_USER_WARNING);
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php
index 2e25df9024..a9b88b1763 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Secrets/SodiumVaultTest.php
@@ -26,19 +26,19 @@ class SodiumVaultTest extends TestCase
$vault = new SodiumVault($this->secretsDir);
$this->assertTrue($vault->generateKeys());
- $this->assertFileExists($this->secretsDir.'/test.sodium.encrypt.public');
- $this->assertFileExists($this->secretsDir.'/test.sodium.decrypt.private');
+ $this->assertFileExists($this->secretsDir.'/test.encrypt.public.php');
+ $this->assertFileExists($this->secretsDir.'/test.decrypt.private.php');
- $encKey = file_get_contents($this->secretsDir.'/test.sodium.encrypt.public');
- $decKey = file_get_contents($this->secretsDir.'/test.sodium.decrypt.private');
+ $encKey = file_get_contents($this->secretsDir.'/test.encrypt.public.php');
+ $decKey = file_get_contents($this->secretsDir.'/test.decrypt.private.php');
$this->assertFalse($vault->generateKeys());
- $this->assertStringEqualsFile($this->secretsDir.'/test.sodium.encrypt.public', $encKey);
- $this->assertStringEqualsFile($this->secretsDir.'/test.sodium.decrypt.private', $decKey);
+ $this->assertStringEqualsFile($this->secretsDir.'/test.encrypt.public.php', $encKey);
+ $this->assertStringEqualsFile($this->secretsDir.'/test.decrypt.private.php', $decKey);
$this->assertTrue($vault->generateKeys(true));
- $this->assertStringNotEqualsFile($this->secretsDir.'/test.sodium.encrypt.public', $encKey);
- $this->assertStringNotEqualsFile($this->secretsDir.'/test.sodium.decrypt.private', $decKey);
+ $this->assertStringNotEqualsFile($this->secretsDir.'/test.encrypt.public.php', $encKey);
+ $this->assertStringNotEqualsFile($this->secretsDir.'/test.decrypt.private.php', $decKey);
}
public function testEncryptAndDecrypt()
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
index ea4a05cfd9..2147d53f12 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
@@ -188,7 +188,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
$checkFunction = sprintf('is_%s', $parameter->getType()->getName());
if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
- throw new InvalidParameterTypeException($this->currentId, \gettype($value), $parameter);
+ throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? \get_class($value) : \gettype($value), $parameter);
}
}
}
diff --git a/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php b/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php
new file mode 100644
index 0000000000..0c547f8a5f
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/EnvVarLoaderInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection;
+
+/**
+ * EnvVarLoaderInterface objects return key/value pairs that are added to the list of available env vars.
+ *
+ * @author Nicolas Grekas
+ */
+interface EnvVarLoaderInterface
+{
+ /**
+ * @return string[] Key/value pairs that can be accessed using the regular "%env()%" syntax
+ */
+ public function loadEnvVars(): array;
+}
diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
index aef13a9ad1..6451ac9b0e 100644
--- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
+++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
+use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
@@ -20,10 +21,17 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
class EnvVarProcessor implements EnvVarProcessorInterface
{
private $container;
+ private $loaders;
+ private $loadedVars = [];
- public function __construct(ContainerInterface $container)
+ /**
+ * @param EnvVarLoaderInterface[] $loaders
+ */
+ public function __construct(ContainerInterface $container, \Traversable $loaders = null)
{
$this->container = $container;
+ $this->loaders = new \IteratorIterator($loaders ?? new \ArrayIterator());
+ $this->loaders = $this->loaders->getInnerIterator();
}
/**
@@ -127,12 +135,31 @@ class EnvVarProcessor implements EnvVarProcessorInterface
} elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
$env = $_SERVER[$name];
} elseif (false === ($env = getenv($name)) || null === $env) { // null is a possible value because of thread safety issues
- if (!$this->container->hasParameter("env($name)")) {
- throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name));
+ foreach ($this->loadedVars as $vars) {
+ if (false !== $env = ($vars[$name] ?? false)) {
+ break;
+ }
}
- if (null === $env = $this->container->getParameter("env($name)")) {
- return null;
+ try {
+ while ((false === $env || null === $env) && $this->loaders->valid()) {
+ $loader = $this->loaders->current();
+ $this->loaders->next();
+ $this->loadedVars[] = $vars = $loader->loadEnvVars();
+ $env = $vars[$name] ?? false;
+ }
+ } catch (ParameterCircularReferenceException $e) {
+ // skip loaders that need an env var that is not defined
+ }
+
+ if (false === $env || null === $env) {
+ if (!$this->container->hasParameter("env($name)")) {
+ throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name));
+ }
+
+ if (null === $env = $this->container->getParameter("env($name)")) {
+ return null;
+ }
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php
index 12d852f453..9970eb474f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php
@@ -5,6 +5,7 @@ namespace Symfony\Component\DependencyInjection\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
use Symfony\Component\DependencyInjection\EnvVarProcessor;
class EnvVarProcessorTest extends TestCase
@@ -517,4 +518,39 @@ CSV;
[null, null],
];
}
+
+ public function testEnvLoader()
+ {
+ $loaders = function () {
+ yield new class() implements EnvVarLoaderInterface {
+ public function loadEnvVars(): array
+ {
+ return [
+ 'FOO_ENV_LOADER' => '123',
+ ];
+ }
+ };
+
+ yield new class() implements EnvVarLoaderInterface {
+ public function loadEnvVars(): array
+ {
+ return [
+ 'FOO_ENV_LOADER' => '234',
+ 'BAR_ENV_LOADER' => '456',
+ ];
+ }
+ };
+ };
+
+ $processor = new EnvVarProcessor(new Container(), $loaders());
+
+ $result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {});
+ $this->assertSame('123', $result);
+
+ $result = $processor->getEnv('string', 'BAR_ENV_LOADER', function () {});
+ $this->assertSame('456', $result);
+
+ $result = $processor->getEnv('string', 'FOO_ENV_LOADER', function () {});
+ $this->assertSame('123', $result); // check twice
+ }
}