diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md
index 5b55f2ece1..20114937ef 100644
--- a/UPGRADE-3.4.md
+++ b/UPGRADE-3.4.md
@@ -52,6 +52,9 @@ DependencyInjection
* Case insensitivity of parameter names is deprecated and will be removed in 4.0.
+ * The `ResolveDefinitionTemplatesPass` class is deprecated and will be removed in 4.0.
+ Use the `ResolveChildDefinitionsPass` class instead.
+
Debug
-----
diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md
index 7c62f06168..ebcd9d3a0b 100644
--- a/UPGRADE-4.0.md
+++ b/UPGRADE-4.0.md
@@ -159,6 +159,9 @@ DependencyInjection
* The `DefinitionDecorator` class has been removed. Use the `ChildDefinition`
class instead.
+ * The `ResolveDefinitionTemplatesPass` class has been removed.
+ Use the `ResolveChildDefinitionsPass` class instead.
+
* Using unsupported configuration keys in YAML configuration files raises an
exception.
diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index b7e096c8d3..97babc5e19 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -73,6 +73,8 @@ CHANGELOG
`EventDispatcherDebugCommand`, `RouterDebugCommand`, `RouterMatchCommand`,
`TranslationDebugCommand`, `TranslationUpdateCommand`, `XliffLintCommand`
and `YamlLintCommand` classes have been marked as final
+ * Deprecated the `web_profiler.position` config option (in Symfony 4.0 the toolbar
+ will always be displayed at the bottom).
3.3.0
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 2b1d582b64..ee5c574603 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -36,6 +36,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
@@ -263,6 +264,8 @@ class FrameworkExtension extends Extension
->addTag('console.command');
$container->registerForAutoconfiguration(ResourceCheckerInterface::class)
->addTag('config_cache.resource_checker');
+ $container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
+ ->addTag('container.env_var_processor');
$container->registerForAutoconfiguration(ServiceSubscriberInterface::class)
->addTag('container.service_subscriber');
$container->registerForAutoconfiguration(ArgumentValueResolverInterface::class)
diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
index f1d850e205..8bfe0bac4d 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
@@ -310,6 +310,9 @@ class SecurityExtension extends Extension
// Provider id (take the first registered provider if none defined)
if (isset($firewall['provider'])) {
$defaultProvider = $this->getUserProviderId($firewall['provider']);
+ if (!in_array($defaultProvider, $providerIds, true)) {
+ throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall['provider']));
+ }
} else {
$defaultProvider = reset($providerIds);
}
@@ -400,7 +403,7 @@ class SecurityExtension extends Extension
$configuredEntryPoint = isset($firewall['entry_point']) ? $firewall['entry_point'] : null;
// Authentication listeners
- list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $configuredEntryPoint);
+ list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint);
$config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint);
@@ -455,7 +458,7 @@ class SecurityExtension extends Extension
return $this->contextListeners[$contextKey] = $listenerId;
}
- private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider, $defaultEntryPoint)
+ private function createAuthenticationListeners($container, $id, $firewall, &$authenticationProviders, $defaultProvider, array $providerIds, $defaultEntryPoint)
{
$listeners = array();
$hasListeners = false;
@@ -465,7 +468,14 @@ class SecurityExtension extends Extension
$key = str_replace('-', '_', $factory->getKey());
if (isset($firewall[$key])) {
- $userProvider = isset($firewall[$key]['provider']) ? $this->getUserProviderId($firewall[$key]['provider']) : $defaultProvider;
+ if (isset($firewall[$key]['provider'])) {
+ if (!in_array($firewall[$key]['provider'], $providerIds, true)) {
+ throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$key]['provider']));
+ }
+ $userProvider = $this->getUserProviderId($firewall[$key]['provider']);
+ } else {
+ $userProvider = $defaultProvider;
+ }
list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
index 4f000d3aee..202107a57a 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php
@@ -387,6 +387,24 @@ abstract class CompleteConfigurationTest extends TestCase
$container = $this->getContainer('access_decision_manager_service_and_strategy');
}
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage Invalid firewall "main": user provider "undefined" not found.
+ */
+ public function testFirewallUndefinedUserProvider()
+ {
+ $this->getContainer('firewall_undefined_provider');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage Invalid firewall "main": user provider "undefined" not found.
+ */
+ public function testFirewallListenerUndefinedProvider()
+ {
+ $this->getContainer('listener_undefined_provider');
+ }
+
protected function getContainer($file)
{
$file = $file.'.'.$this->getFileExtension();
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php
new file mode 100644
index 0000000000..78d461efe3
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/firewall_undefined_provider.php
@@ -0,0 +1,17 @@
+loadFromExtension('security', array(
+ 'providers' => array(
+ 'default' => array(
+ 'memory' => array(
+ 'users' => array('foo' => array('password' => 'foo', 'roles' => 'ROLE_USER')),
+ ),
+ ),
+ ),
+ 'firewalls' => array(
+ 'main' => array(
+ 'provider' => 'undefined',
+ 'form_login' => true,
+ ),
+ ),
+));
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php
new file mode 100644
index 0000000000..da54f025d1
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/listener_undefined_provider.php
@@ -0,0 +1,16 @@
+loadFromExtension('security', array(
+ 'providers' => array(
+ 'default' => array(
+ 'memory' => array(
+ 'users' => array('foo' => array('password' => 'foo', 'roles' => 'ROLE_USER')),
+ ),
+ ),
+ ),
+ 'firewalls' => array(
+ 'main' => array(
+ 'form_login' => array('provider' => 'undefined'),
+ ),
+ ),
+));
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml
new file mode 100644
index 0000000000..f596ac5a62
--- /dev/null
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/firewall_undefined_provider.xml
@@ -0,0 +1,20 @@
+
+
+
+ */
+class RegisterEnvVarProcessorsPass implements CompilerPassInterface
+{
+ private static $allowedTypes = array('array', 'bool', 'float', 'int', 'string');
+
+ public function process(ContainerBuilder $container)
+ {
+ $bag = $container->getParameterBag();
+ $types = array();
+ $processors = array();
+ foreach ($container->findTaggedServiceIds('container.env_var_processor') as $id => $tags) {
+ foreach ($tags as $attr) {
+ if (!$r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass())) {
+ throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ } elseif (!$r->isSubclassOf(EnvVarProcessorInterface::class)) {
+ throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
+ }
+ foreach ($class::getProvidedTypes() as $prefix => $type) {
+ $processors[$prefix] = new ServiceClosureArgument(new Reference($id));
+ $types[$prefix] = self::validateProvidedTypes($type, $class);
+ }
+ }
+ }
+
+ if ($processors) {
+ if ($bag instanceof EnvPlaceholderParameterBag) {
+ $bag->setProvidedTypes($types);
+ }
+ $container->register('container.env_var_processors_locator', ServiceLocator::class)
+ ->setArguments(array($processors))
+ ;
+ }
+ }
+
+ private static function validateProvidedTypes($types, $class)
+ {
+ $types = explode('|', $types);
+
+ foreach ($types as $type) {
+ if (!in_array($type, self::$allowedTypes)) {
+ throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::$allowedTypes)));
+ }
+ }
+
+ return $types;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php
similarity index 98%
rename from src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php
rename to src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php
index dcede86a18..3b197a624e 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveChildDefinitionsPass.php
@@ -23,7 +23,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
* @author Johannes M. Schmitt
*/
-class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
+class ResolveChildDefinitionsPass extends AbstractRecursivePass
{
protected function processValue($value, $isRoot = false)
{
diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php
index 4187a3bba5..1d4b0f1769 100644
--- a/src/Symfony/Component/DependencyInjection/Container.php
+++ b/src/Symfony/Component/DependencyInjection/Container.php
@@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
@@ -48,9 +49,11 @@ class Container implements ResettableContainerInterface
protected $methodMap = array();
protected $aliases = array();
protected $loading = array();
+ protected $resolving = array();
private $envCache = array();
private $compiled = false;
+ private $getEnv;
/**
* @param ParameterBagInterface $parameterBag A ParameterBagInterface instance
@@ -336,23 +339,37 @@ class Container implements ResettableContainerInterface
*/
protected function getEnv($name)
{
+ if (isset($this->resolving[$envName = "env($name)"])) {
+ throw new ParameterCircularReferenceException(array_keys($this->resolving));
+ }
if (isset($this->envCache[$name]) || array_key_exists($name, $this->envCache)) {
return $this->envCache[$name];
}
- if (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
- return $this->envCache[$name] = $_SERVER[$name];
+ if (!$this->has($id = 'container.env_var_processors_locator')) {
+ $this->set($id, new ServiceLocator(array()));
}
- if (isset($_ENV[$name])) {
- return $this->envCache[$name] = $_ENV[$name];
- }
- if (false !== ($env = getenv($name)) && null !== $env) { // null is a possible value because of thread safety issues
- return $this->envCache[$name] = $env;
- }
- if (!$this->hasParameter("env($name)")) {
- throw new EnvNotFoundException($name);
+ if (!$this->getEnv) {
+ $this->getEnv = new \ReflectionMethod($this, __FUNCTION__);
+ $this->getEnv->setAccessible(true);
+ $this->getEnv = $this->getEnv->getClosure($this);
}
+ $processors = $this->get($id);
- return $this->envCache[$name] = $this->getParameter("env($name)");
+ if (false !== $i = strpos($name, ':')) {
+ $prefix = substr($name, 0, $i);
+ $localName = substr($name, 1 + $i);
+ } else {
+ $prefix = 'string';
+ $localName = $name;
+ }
+ $processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this);
+
+ $this->resolving[$envName] = true;
+ try {
+ return $this->envCache[$name] = $processor->getEnv($prefix, $localName, $this->getEnv);
+ } finally {
+ unset($this->resolving[$envName]);
+ }
}
private function __clone()
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 2c188a621f..52c8b3d618 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -1396,20 +1396,26 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
protected function getEnv($name)
{
$value = parent::getEnv($name);
+ $bag = $this->getParameterBag();
- if (!is_string($value) || !$this->getParameterBag() instanceof EnvPlaceholderParameterBag) {
+ if (!is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) {
return $value;
}
- foreach ($this->getParameterBag()->getEnvPlaceholders() as $env => $placeholders) {
+ foreach ($bag->getEnvPlaceholders() as $env => $placeholders) {
if (isset($placeholders[$value])) {
- $bag = new ParameterBag($this->getParameterBag()->all());
+ $bag = new ParameterBag($bag->all());
return $bag->unescapeValue($bag->get("env($name)"));
}
}
- return $value;
+ $this->resolving["env($name)"] = true;
+ try {
+ return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true));
+ } finally {
+ unset($this->resolving["env($name)"]);
+ }
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index f55f6db012..62eb0a94cf 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -1047,7 +1047,7 @@ EOF;
$export = $this->exportParameters(array($value));
$export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2);
- if (preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $export[1])) {
+ if (preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $export[1])) {
$dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]);
} else {
$php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
@@ -1590,7 +1590,7 @@ EOF;
return $dumpedValue;
}
- if (!preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) {
+ if (!preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) {
return sprintf("\$this->parameters['%s']", $name);
}
}
@@ -1790,13 +1790,16 @@ EOF;
{
$export = var_export($value, true);
- if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('%s').'")) {
+ if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) {
$export = $resolvedExport;
- if ("'" === $export[1]) {
- $export = substr($export, 3);
- }
if (".''" === substr($export, -3)) {
$export = substr($export, 0, -3);
+ if ("'" === $export[1]) {
+ $export = substr_replace($export, '', 18, 7);
+ }
+ }
+ if ("'" === $export[1]) {
+ $export = substr($export, 3);
}
}
diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
new file mode 100644
index 0000000000..51a19367e2
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
@@ -0,0 +1,160 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection;
+
+use Symfony\Component\Config\Util\XmlUtils;
+use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+
+class EnvVarProcessor implements EnvVarProcessorInterface
+{
+ private $container;
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getProvidedTypes()
+ {
+ return array(
+ 'base64' => 'string',
+ 'bool' => 'bool',
+ 'const' => 'bool|int|float|string|array',
+ 'file' => 'string',
+ 'float' => 'float',
+ 'int' => 'int',
+ 'json' => 'array',
+ 'resolve' => 'string',
+ 'string' => 'string',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getEnv($prefix, $name, \Closure $getEnv)
+ {
+ $i = strpos($name, ':');
+
+ if ('file' === $prefix) {
+ if (!is_scalar($file = $getEnv($name))) {
+ throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name));
+ }
+ if (!file_exists($file)) {
+ throw new RuntimeException(sprintf('Env "file:%s" not found: %s does not exist.', $name, $file));
+ }
+
+ return file_get_contents($file);
+ }
+
+ if (false !== $i || 'string' !== $prefix) {
+ if (null === $env = $getEnv($name)) {
+ return;
+ }
+ } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
+ $env = $_SERVER[$name];
+ } elseif (isset($_ENV[$name])) {
+ $env = $_ENV[$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($name);
+ }
+
+ if (null === $env = $this->container->getParameter("env($name)")) {
+ return;
+ }
+ }
+
+ if (!is_scalar($env)) {
+ throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to %s.', $name, $prefix));
+ }
+
+ if ('string' === $prefix) {
+ return (string) $env;
+ }
+
+ if ('bool' === $prefix) {
+ return (bool) self::phpize($env);
+ }
+
+ if ('int' === $prefix) {
+ if (!is_numeric($env = self::phpize($env))) {
+ throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.', $name));
+ }
+
+ return (int) $env;
+ }
+
+ if ('float' === $prefix) {
+ if (!is_numeric($env = self::phpize($env))) {
+ throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.', $name));
+ }
+
+ return (float) $env;
+ }
+
+ if ('const' === $prefix) {
+ if (!defined($env)) {
+ throw new RuntimeException(sprintf('Env var "%s" maps to undefined constant "%s".', $name, $env));
+ }
+
+ return constant($name);
+ }
+
+ if ('base64' === $prefix) {
+ return base64_decode($env);
+ }
+
+ if ('json' === $prefix) {
+ $env = json_decode($env, true, JSON_BIGINT_AS_STRING);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new RuntimeException(sprintf('Invalid JSON in env var "%s": '.json_last_error_msg(), $name));
+ }
+
+ if (!is_array($env)) {
+ throw new RuntimeException(sprintf('Invalid JSON env var "%s": array expected, %s given.', $name, gettype($env)));
+ }
+
+ return $env;
+ }
+
+ if ('resolve' === $prefix) {
+ return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name) {
+ if (!isset($match[1])) {
+ return '%';
+ }
+ $value = $this->container->getParameter($match[1]);
+ if (!is_scalar($value)) {
+ throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, gettype($value)));
+ }
+
+ return $value;
+ }, $env);
+ }
+
+ throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix));
+ }
+
+ private static function phpize($value)
+ {
+ if (!class_exists(XmlUtils::class)) {
+ throw new RuntimeException('The Symfony Config component is required to cast env vars to "bool", "int" or "float".');
+ }
+
+ return XmlUtils::phpize($value);
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessorInterface.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessorInterface.php
new file mode 100644
index 0000000000..fce9f5cd7a
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessorInterface.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection;
+
+/**
+ * The EnvVarProcessorInterface is implemented by objects that manage environment-like variables.
+ *
+ * @author Nicolas Grekas
+ */
+interface EnvVarProcessorInterface
+{
+ /**
+ * Returns the value of the given variable as managed by the current instance.
+ *
+ * @param string $prefix The namespace of the variable
+ * @param string $name The name of the variable within the namespace
+ * @param \Closure $getEnv A closure that allows fetching more env vars
+ *
+ * @return mixed
+ *
+ * @throws RuntimeException on error
+ */
+ public function getEnv($prefix, $name, \Closure $getEnv);
+
+ /**
+ * @return string[] The PHP-types managed by getEnv(), keyed by prefixes
+ */
+ public static function getProvidedTypes();
+}
diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php
index fd859068be..e0fe4c7cdf 100644
--- a/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php
+++ b/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php
@@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
class EnvPlaceholderParameterBag extends ParameterBag
{
private $envPlaceholders = array();
+ private $providedTypes = array();
/**
* {@inheritdoc}
@@ -34,7 +35,7 @@ class EnvPlaceholderParameterBag extends ParameterBag
return $placeholder; // return first result
}
}
- if (preg_match('/\W/', $env)) {
+ if (!preg_match('/^(?:\w++:)*+\w++$/', $env)) {
throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name));
}
@@ -80,6 +81,24 @@ class EnvPlaceholderParameterBag extends ParameterBag
}
}
+ /**
+ * Maps env prefixes to their corresponding PHP types.
+ */
+ public function setProvidedTypes(array $providedTypes)
+ {
+ $this->providedTypes = $providedTypes;
+ }
+
+ /**
+ * Gets the PHP types corresponding to env() parameter prefixes.
+ *
+ * @return string[][]
+ */
+ public function getProvidedTypes()
+ {
+ return $this->providedTypes;
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php
new file mode 100644
index 0000000000..dc3a1f1834
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Tests\Compiler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Compiler\RegisterEnvVarProcessorsPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
+
+class RegisterEnvVarProcessorsPassTest extends TestCase
+{
+ public function testSimpleProcessor()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', SimpleProcessor::class)->addTag('container.env_var_processor');
+
+ (new RegisterEnvVarProcessorsPass())->process($container);
+
+ $this->assertTrue($container->has('container.env_var_processors_locator'));
+ $this->assertInstanceof(SimpleProcessor::class, $container->get('container.env_var_processors_locator')->get('foo'));
+
+ $this->assertSame(array('foo' => array('string')), $container->getParameterBag()->getProvidedTypes());
+ }
+
+ public function testNoProcessor()
+ {
+ $container = new ContainerBuilder();
+
+ (new RegisterEnvVarProcessorsPass())->process($container);
+
+ $this->assertFalse($container->has('container.env_var_processors_locator'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Invalid type "foo" returned by "Symfony\Component\DependencyInjection\Tests\Compiler\BadProcessor::getProvidedTypes()", expected one of "array", "bool", "float", "int", "string".
+ */
+ public function testBadProcessor()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', BadProcessor::class)->addTag('container.env_var_processor');
+
+ (new RegisterEnvVarProcessorsPass())->process($container);
+ }
+}
+
+class SimpleProcessor implements EnvVarProcessorInterface
+{
+ public function getEnv($prefix, $name, \Closure $getEnv)
+ {
+ return $getEnv($name);
+ }
+
+ public static function getProvidedTypes()
+ {
+ return array('foo' => 'string');
+ }
+}
+
+class BadProcessor extends SimpleProcessor
+{
+ public static function getProvidedTypes()
+ {
+ return array('foo' => 'string|foo');
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php
similarity index 98%
rename from src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php
rename to src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php
index 323aa003ef..d15be74ecd 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php
@@ -13,10 +13,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ChildDefinition;
-use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass;
+use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
-class ResolveDefinitionTemplatesPassTest extends TestCase
+class ResolveChildDefinitionsPassTest extends TestCase
{
public function testProcess()
{
@@ -372,7 +372,7 @@ class ResolveDefinitionTemplatesPassTest extends TestCase
protected function process(ContainerBuilder $container)
{
- $pass = new ResolveDefinitionTemplatesPass();
+ $pass = new ResolveChildDefinitionsPass();
$pass->process($container);
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php
index 164ab25941..c97fe1daba 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php
@@ -14,7 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
-use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass;
+use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ResolveInstanceofConditionalsPassTest extends TestCase
@@ -57,7 +57,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
$container->setDefinition('child', $def);
(new ResolveInstanceofConditionalsPass())->process($container);
- (new ResolveDefinitionTemplatesPass())->process($container);
+ (new ResolveChildDefinitionsPass())->process($container);
$expected = array(
array('foo', array('bar')),
@@ -95,7 +95,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
));
(new ResolveInstanceofConditionalsPass())->process($container);
- (new ResolveDefinitionTemplatesPass())->process($container);
+ (new ResolveChildDefinitionsPass())->process($container);
$def = $container->getDefinition('foo');
$this->assertTrue($def->isAutowired());
@@ -119,7 +119,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
->setFactory('autoconfigured_factory');
(new ResolveInstanceofConditionalsPass())->process($container);
- (new ResolveDefinitionTemplatesPass())->process($container);
+ (new ResolveChildDefinitionsPass())->process($container);
$def = $container->getDefinition('normal_service');
// autowired thanks to the autoconfigured instanceof
@@ -147,7 +147,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
;
(new ResolveInstanceofConditionalsPass())->process($container);
- (new ResolveDefinitionTemplatesPass())->process($container);
+ (new ResolveChildDefinitionsPass())->process($container);
$def = $container->getDefinition('normal_service');
$this->assertSame(array('duplicated_tag' => array(array(), array('and_attributes' => 1))), $def->getTags());
@@ -165,7 +165,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
->setAutowired(true);
(new ResolveInstanceofConditionalsPass())->process($container);
- (new ResolveDefinitionTemplatesPass())->process($container);
+ (new ResolveChildDefinitionsPass())->process($container);
$def = $container->getDefinition('normal_service');
$this->assertFalse($def->isAutowired());
diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
index 53e470d77a..648542db65 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
@@ -665,6 +665,70 @@ class ContainerBuilderTest extends TestCase
$container->compile(true);
}
+ public function testDynamicEnv()
+ {
+ putenv('DUMMY_FOO=some%foo%');
+ putenv('DUMMY_BAR=%bar%');
+
+ $container = new ContainerBuilder();
+ $container->setParameter('foo', 'Foo%env(resolve:DUMMY_BAR)%');
+ $container->setParameter('bar', 'Bar');
+ $container->setParameter('baz', '%env(resolve:DUMMY_FOO)%');
+
+ $container->compile(true);
+ putenv('DUMMY_FOO');
+ putenv('DUMMY_BAR');
+
+ $this->assertSame('someFooBar', $container->getParameter('baz'));
+ }
+
+ public function testCastEnv()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('env(FAKE)', '123');
+
+ $container->register('foo', 'stdClass')->setProperties(array(
+ 'fake' => '%env(int:FAKE)%',
+ ));
+
+ $container->compile(true);
+
+ $this->assertSame(123, $container->get('foo')->fake);
+ }
+
+ public function testEnvAreNullable()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('env(FAKE)', null);
+
+ $container->register('foo', 'stdClass')->setProperties(array(
+ 'fake' => '%env(int:FAKE)%',
+ ));
+
+ $container->compile(true);
+
+ $this->assertNull($container->get('foo')->fake);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException
+ * @expectedExceptionMessage Circular reference detected for parameter "env(resolve:DUMMY_ENV_VAR)" ("env(resolve:DUMMY_ENV_VAR)" > "env(resolve:DUMMY_ENV_VAR)").
+ */
+ public function testCircularDynamicEnv()
+ {
+ putenv('DUMMY_ENV_VAR=some%foo%');
+
+ $container = new ContainerBuilder();
+ $container->setParameter('foo', '%bar%');
+ $container->setParameter('bar', '%env(resolve:DUMMY_ENV_VAR)%');
+
+ try {
+ $container->compile(true);
+ } finally {
+ putenv('DUMMY_ENV_VAR');
+ }
+ }
+
/**
* @expectedException \LogicException
*/
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index 2ae2ab0f6d..4e108a30af 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
+use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator;
@@ -318,13 +319,82 @@ class PhpDumperTest extends TestCase
public function testEnvParameter()
{
+ $rand = mt_rand();
+ putenv('Baz='.$rand);
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services26.yml');
+ $container->setParameter('env(json_file)', self::$fixturesPath.'/array.json');
$container->compile();
$dumper = new PhpDumper($container);
- $this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container');
+ $this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_EnvParameters', 'file' => self::$fixturesPath.'/php/services26.php')));
+
+ require self::$fixturesPath.'/php/services26.php';
+ $container = new \Symfony_DI_PhpDumper_Test_EnvParameters();
+ $this->assertSame($rand, $container->getParameter('baz'));
+ $this->assertSame(array(123, 'abc'), $container->getParameter('json'));
+ $this->assertSame('sqlite:///foo/bar/var/data.db', $container->getParameter('db_dsn'));
+ putenv('Baz');
+ }
+
+ public function testResolvedBase64EnvParameters()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('env(foo)', base64_encode('world'));
+ $container->setParameter('hello', '%env(base64:foo)%');
+ $container->compile(true);
+
+ $expected = array(
+ 'env(foo)' => 'd29ybGQ=',
+ 'hello' => 'world',
+ );
+ $this->assertSame($expected, $container->getParameterBag()->all());
+ }
+
+ public function testDumpedBase64EnvParameters()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('env(foo)', base64_encode('world'));
+ $container->setParameter('hello', '%env(base64:foo)%');
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ $dumper->dump();
+
+ $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_base64_env.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Base64Parameters')));
+
+ require self::$fixturesPath.'/php/services_base64_env.php';
+ $container = new \Symfony_DI_PhpDumper_Test_Base64Parameters();
+ $this->assertSame('world', $container->getParameter('hello'));
+ }
+
+ public function testCustomEnvParameters()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('env(foo)', str_rot13('world'));
+ $container->setParameter('hello', '%env(rot13:foo)%');
+ $container->register(Rot13EnvVarProcessor::class)->addTag('container.env_var_processor');
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ $dumper->dump();
+
+ $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_rot13_env.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Rot13Parameters')));
+
+ require self::$fixturesPath.'/php/services_rot13_env.php';
+ $container = new \Symfony_DI_PhpDumper_Test_Rot13Parameters();
+ $this->assertSame('world', $container->getParameter('hello'));
+ }
+
+ public function testFileEnvProcessor()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('env(foo)', __FILE__);
+ $container->setParameter('random', '%env(file:foo)%');
+ $container->compile(true);
+
+ $this->assertStringEqualsFile(__FILE__, $container->getParameter('random'));
}
/**
@@ -340,6 +410,31 @@ class PhpDumperTest extends TestCase
$dumper->dump();
}
+ /**
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException
+ * @expectedExceptionMessage Circular reference detected for parameter "env(resolve:DUMMY_ENV_VAR)" ("env(resolve:DUMMY_ENV_VAR)" > "env(resolve:DUMMY_ENV_VAR)").
+ */
+ public function testCircularDynamicEnv()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('foo', '%bar%');
+ $container->setParameter('bar', '%env(resolve:DUMMY_ENV_VAR)%');
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ $dump = $dumper->dump(array('class' => $class = __FUNCTION__));
+
+ eval('?>'.$dump);
+ $container = new $class();
+
+ putenv('DUMMY_ENV_VAR=%foo%');
+ try {
+ $container->getParameter('bar');
+ } finally {
+ putenv('DUMMY_ENV_VAR');
+ }
+ }
+
public function testInlinedDefinitionReferencingServiceContainer()
{
$container = new ContainerBuilder();
@@ -703,3 +798,16 @@ class PhpDumperTest extends TestCase
$this->assertSame('foo', $container->getParameter('BAR'));
}
}
+
+class Rot13EnvVarProcessor implements EnvVarProcessorInterface
+{
+ public function getEnv($prefix, $name, \Closure $getEnv)
+ {
+ return str_rot13($getEnv($name));
+ }
+
+ public static function getProvidedTypes()
+ {
+ return array('rot13' => 'string');
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/array.json b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/array.json
new file mode 100644
index 0000000000..dc27f0fa15
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/array.json
@@ -0,0 +1 @@
+[123, "abc"]
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php
index 5d5978f1fc..d08b92947b 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php
@@ -9,14 +9,14 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
- * ProjectServiceContainer.
+ * Symfony_DI_PhpDumper_Test_EnvParameters.
*
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
-class ProjectServiceContainer extends Container
+class Symfony_DI_PhpDumper_Test_EnvParameters extends Container
{
private $parameters;
private $targetDirs = array();
@@ -27,6 +27,10 @@ class ProjectServiceContainer extends Container
*/
public function __construct()
{
+ $dir = __DIR__;
+ for ($i = 1; $i <= 5; ++$i) {
+ $this->targetDirs[$i] = $dir = dirname($dir);
+ }
$this->parameters = $this->getDefaultParameters();
$this->services = $this->privates = array();
@@ -71,7 +75,7 @@ class ProjectServiceContainer extends Container
{
$class = $this->getEnv('FOO');
- return $this->services['test'] = new $class($this->getEnv('Bar'), 'foo'.$this->getEnv('FOO').'baz');
+ return $this->services['test'] = new $class($this->getEnv('Bar'), 'foo'.$this->getEnv('string:FOO').'baz', $this->getEnv('int:Baz'));
}
/**
@@ -127,6 +131,10 @@ class ProjectServiceContainer extends Container
private $loadedDynamicParameters = array(
'bar' => false,
+ 'baz' => false,
+ 'json' => false,
+ 'db_dsn' => false,
+ 'env(json_file)' => false,
);
private $dynamicParameters = array();
@@ -143,6 +151,10 @@ class ProjectServiceContainer extends Container
{
switch ($name) {
case 'bar': $value = $this->getEnv('FOO'); break;
+ case 'baz': $value = $this->getEnv('int:Baz'); break;
+ case 'json': $value = $this->getEnv('json:file:json_file'); break;
+ case 'db_dsn': $value = $this->getEnv('resolve:DB'); break;
+ case 'env(json_file)': $value = ($this->targetDirs[1].'/array.json'); break;
default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name));
}
$this->loadedDynamicParameters[$name] = true;
@@ -158,7 +170,9 @@ class ProjectServiceContainer extends Container
protected function getDefaultParameters()
{
return array(
+ 'project_dir' => '/foo/bar',
'env(FOO)' => 'foo',
+ 'env(DB)' => 'sqlite://%project_dir%/var/data.db',
);
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php
new file mode 100644
index 0000000000..891d445604
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_base64_env.php
@@ -0,0 +1,149 @@
+parameters = $this->getDefaultParameters();
+
+ $this->services = $this->privates = array();
+
+ $this->aliases = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->privates = array();
+ parent::reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compile()
+ {
+ throw new LogicException('You cannot compile a dumped container that was already compiled.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isCompiled()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParameter($name)
+ {
+ $name = (string) $name;
+
+ if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
+ throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
+ }
+ if (isset($this->loadedDynamicParameters[$name])) {
+ return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
+ }
+
+ return $this->parameters[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasParameter($name)
+ {
+ $name = (string) $name;
+
+ return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setParameter($name, $value)
+ {
+ throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParameterBag()
+ {
+ if (null === $this->parameterBag) {
+ $parameters = $this->parameters;
+ foreach ($this->loadedDynamicParameters as $name => $loaded) {
+ $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
+ }
+ $this->parameterBag = new FrozenParameterBag($parameters);
+ }
+
+ return $this->parameterBag;
+ }
+
+ private $loadedDynamicParameters = array(
+ 'hello' => false,
+ );
+ private $dynamicParameters = array();
+
+ /**
+ * Computes a dynamic parameter.
+ *
+ * @param string The name of the dynamic parameter to load
+ *
+ * @return mixed The value of the dynamic parameter
+ *
+ * @throws InvalidArgumentException When the dynamic parameter does not exist
+ */
+ private function getDynamicParameter($name)
+ {
+ switch ($name) {
+ case 'hello': $value = $this->getEnv('base64:foo'); break;
+ default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name));
+ }
+ $this->loadedDynamicParameters[$name] = true;
+
+ return $this->dynamicParameters[$name] = $value;
+ }
+
+ /**
+ * Gets the default parameters.
+ *
+ * @return array An array of the default parameters
+ */
+ protected function getDefaultParameters()
+ {
+ return array(
+ 'env(foo)' => 'd29ybGQ=',
+ );
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
new file mode 100644
index 0000000000..c0d94ffa4c
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
@@ -0,0 +1,175 @@
+parameters = $this->getDefaultParameters();
+
+ $this->services = $this->privates = array();
+ $this->methodMap = array(
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\Rot13EnvVarProcessor' => 'getRot13EnvVarProcessorService',
+ 'container.env_var_processors_locator' => 'getContainer_EnvVarProcessorsLocatorService',
+ );
+
+ $this->aliases = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->privates = array();
+ parent::reset();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compile()
+ {
+ throw new LogicException('You cannot compile a dumped container that was already compiled.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isCompiled()
+ {
+ return true;
+ }
+
+ /**
+ * Gets the public 'Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor' shared service.
+ *
+ * @return \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor
+ */
+ protected function getRot13EnvVarProcessorService()
+ {
+ return $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor();
+ }
+
+ /**
+ * Gets the public 'container.env_var_processors_locator' shared service.
+ *
+ * @return \Symfony\Component\DependencyInjection\ServiceLocator
+ */
+ protected function getContainer_EnvVarProcessorsLocatorService()
+ {
+ return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('rot13' => function () {
+ return ($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor());
+ }));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParameter($name)
+ {
+ $name = (string) $name;
+
+ if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
+ throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
+ }
+ if (isset($this->loadedDynamicParameters[$name])) {
+ return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
+ }
+
+ return $this->parameters[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasParameter($name)
+ {
+ $name = (string) $name;
+
+ return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setParameter($name, $value)
+ {
+ throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParameterBag()
+ {
+ if (null === $this->parameterBag) {
+ $parameters = $this->parameters;
+ foreach ($this->loadedDynamicParameters as $name => $loaded) {
+ $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
+ }
+ $this->parameterBag = new FrozenParameterBag($parameters);
+ }
+
+ return $this->parameterBag;
+ }
+
+ private $loadedDynamicParameters = array(
+ 'hello' => false,
+ );
+ private $dynamicParameters = array();
+
+ /**
+ * Computes a dynamic parameter.
+ *
+ * @param string The name of the dynamic parameter to load
+ *
+ * @return mixed The value of the dynamic parameter
+ *
+ * @throws InvalidArgumentException When the dynamic parameter does not exist
+ */
+ private function getDynamicParameter($name)
+ {
+ switch ($name) {
+ case 'hello': $value = $this->getEnv('rot13:foo'); break;
+ default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name));
+ }
+ $this->loadedDynamicParameters[$name] = true;
+
+ return $this->dynamicParameters[$name] = $value;
+ }
+
+ /**
+ * Gets the default parameters.
+ *
+ * @return array An array of the default parameters
+ */
+ protected function getDefaultParameters()
+ {
+ return array(
+ 'env(foo)' => 'jbeyq',
+ );
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml
index 2ef23c1af5..d573e810db 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml
@@ -1,6 +1,11 @@
parameters:
+ project_dir: '/foo/bar'
env(FOO): foo
+ env(DB): 'sqlite://%%project_dir%%/var/data.db'
bar: '%env(FOO)%'
+ baz: '%env(int:Baz)%'
+ json: '%env(json:file:json_file)%'
+ db_dsn: '%env(resolve:DB)%'
services:
test:
@@ -8,3 +13,4 @@ services:
arguments:
- '%env(Bar)%'
- 'foo%bar%baz'
+ - '%baz%'