diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 32f934eef458..9935b8a58052 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * added `%env(trim:...)%` processor to trim a string value * added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%` + * added `%env(url:...)%` processor to convert an URL or DNS into an array of components + * added `%env(query_string:...)%` processor to convert a query string into an array of key values * added support for deprecating aliases * made `ContainerParametersResource` final and not implement `Serializable` anymore * added `ReverseContainer`: a container that turns services back to their ids diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index d734cf5b8fc1..834ca51f3c76 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -41,6 +41,8 @@ public static function getProvidedTypes() 'int' => 'int', 'json' => 'array', 'key' => 'bool|int|float|string|array', + 'url' => 'array', + 'query_string' => 'array', 'resolve' => 'string', 'default' => 'bool|int|float|string|array', 'string' => 'string', @@ -183,6 +185,37 @@ public function getEnv($prefix, $name, \Closure $getEnv) return $env; } + if ('url' === $prefix) { + $parsedEnv = parse_url($env); + + if (false === $parsedEnv) { + throw new RuntimeException(sprintf('Invalid URL in env var "%s"', $name)); + } + if (!isset($parsedEnv['scheme'], $parsedEnv['host'])) { + throw new RuntimeException(sprintf('Invalid URL env var "%s": schema and host expected, %s given.', $name, $env)); + } + $parsedEnv += [ + 'port' => null, + 'user' => null, + 'pass' => null, + 'path' => null, + 'query' => null, + 'fragment' => null, + ]; + + // remove the '/' separator + $parsedEnv['path'] = '/' === $parsedEnv['path'] ? null : substr($parsedEnv['path'], 1); + + return $parsedEnv; + } + + if ('query_string' === $prefix) { + $queryString = parse_url($env, PHP_URL_QUERY) ?: $env; + parse_str($queryString, $result); + + return $result; + } + if ('resolve' === $prefix) { return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name) { if (!isset($match[1])) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php index f376165dfc0a..bcb8e2473c92 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterEnvVarProcessorsPassTest.php @@ -39,6 +39,8 @@ public function testSimpleProcessor() 'int' => ['int'], 'json' => ['array'], 'key' => ['bool', 'int', 'float', 'string', 'array'], + 'url' => ['array'], + 'query_string' => ['array'], 'resolve' => ['string'], 'default' => ['bool', 'int', 'float', 'string', 'array'], 'string' => ['string'], diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 828439969ef3..fa921f14b1c4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -497,6 +497,52 @@ public function testDumpedDefaultEnvParameters() $this->assertSame('foobaz', $container->getParameter('hello-bar')); } + public function testDumpedUrlEnvParameters() + { + $container = new ContainerBuilder(); + $container->setParameter('env(foo)', 'postgres://user@localhost:5432/database?sslmode=disable'); + $container->setParameter('hello', '%env(url:foo)%'); + $container->compile(); + + $dumper = new PhpDumper($container); + $dumper->dump(); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_url_env.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_UrlParameters'])); + + require self::$fixturesPath.'/php/services_url_env.php'; + $container = new \Symfony_DI_PhpDumper_Test_UrlParameters(); + $this->assertSame([ + 'scheme' => 'postgres', + 'host' => 'localhost', + 'port' => 5432, + 'user' => 'user', + 'path' => 'database', + 'query' => 'sslmode=disable', + 'pass' => null, + 'fragment' => null, + ], $container->getParameter('hello')); + } + + public function testDumpedQueryEnvParameters() + { + $container = new ContainerBuilder(); + $container->setParameter('env(foo)', 'foo=bar&baz[]=qux'); + $container->setParameter('hello', '%env(query_string:foo)%'); + $container->compile(); + + $dumper = new PhpDumper($container); + $dumper->dump(); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_query_string_env.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_QueryStringParameters'])); + + require self::$fixturesPath.'/php/services_query_string_env.php'; + $container = new \Symfony_DI_PhpDumper_Test_QueryStringParameters(); + $this->assertSame([ + 'foo' => 'bar', + 'baz' => ['qux'], + ], $container->getParameter('hello')); + } + public function testDumpedJsonEnvParameters() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php new file mode 100644 index 000000000000..e69a22b12931 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_query_string_env.php @@ -0,0 +1,124 @@ +parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + + $this->aliases = []; + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return [ + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ]; + } + + 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]; + } + + public function hasParameter($name) + { + $name = (string) $name; + + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters); + } + + public function setParameter($name, $value) + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + 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 = [ + 'hello' => false, + ]; + private $dynamicParameters = []; + + /** + * Computes a dynamic parameter. + * + * @param string $name 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('query_string: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 [ + 'env(foo)' => 'foo=bar&baz[]=qux', + ]; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php new file mode 100644 index 000000000000..bd0d9105d7cc --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_url_env.php @@ -0,0 +1,124 @@ +parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + + $this->aliases = []; + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return [ + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ]; + } + + 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]; + } + + public function hasParameter($name) + { + $name = (string) $name; + + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters); + } + + public function setParameter($name, $value) + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + 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 = [ + 'hello' => false, + ]; + private $dynamicParameters = []; + + /** + * Computes a dynamic parameter. + * + * @param string $name 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('url: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 [ + 'env(foo)' => 'postgres://user@localhost:5432/database?sslmode=disable', + ]; + } +}