Skip to content

Commit

Permalink
feature #28976 [DI] Add a "default" EnvProcessor (jderusse)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 4.3-dev branch (closes #28976).

Discussion
----------

[DI] Add a "default" EnvProcessor

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | TODO

This PR add a new fallback env processor in order to return a default value when the primary processor is not able to fetch a value (env variable, file or key does not exists)

```
#
default_host: localhost
host: '%env(default:default_host:OPTIONAL_ENV_VARIABLE)%"

default_secret: this secret is not secret
secret: '%env(default:default_secret:file:THIS_FILE_ONLY_EXIST_IN_PRODUCTION)%"

default_charset: utf8
charset: '%env(default:default_charset:key:charset:json:DATABASE_CONFIG)%"
```

Commits
-------

aee4e33 [DI] Add a \"default\" EnvProcessor
  • Loading branch information
nicolas-grekas committed Dec 1, 2018
2 parents 4df912b + aee4e33 commit 67be665
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 11 deletions.
5 changes: 5 additions & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

4.3.0
-----

* added `%env(default:...)%` processor to fallback to a default value

4.2.0
-----

Expand Down
31 changes: 26 additions & 5 deletions src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
Expand Up @@ -43,6 +43,7 @@ public static function getProvidedTypes()
'json' => 'array',
'key' => 'bool|int|float|string|array',
'resolve' => 'string',
'default' => 'bool|int|float|string|array',
'string' => 'string',
);
}
Expand All @@ -56,7 +57,7 @@ public function getEnv($prefix, $name, \Closure $getEnv)

if ('key' === $prefix) {
if (false === $i) {
throw new RuntimeException(sprintf('Invalid configuration: env var "key:%s" does not contain a key specifier.', $name));
throw new RuntimeException(sprintf('Invalid env "key:%s": a key specifier should be provided.', $name));
}

$next = substr($name, $i + 1);
Expand All @@ -66,19 +67,39 @@ public function getEnv($prefix, $name, \Closure $getEnv)
if (!\is_array($array)) {
throw new RuntimeException(sprintf('Resolved value of "%s" did not result in an array value.', $next));
}
if (!array_key_exists($key, $array)) {
throw new RuntimeException(sprintf('Key "%s" not found in "%s" (resolved from "%s")', $key, json_encode($array), $next));

if (!isset($array[$key]) && !array_key_exists($key, $array)) {
throw new EnvNotFoundException(sprintf('Key "%s" not found in "%s" (resolved from "%s").', $key, json_encode($array), $next));
}

return $array[$key];
}

if ('default' === $prefix) {
if (false === $i) {
throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name));
}

$next = substr($name, $i + 1);
$default = substr($name, 0, $i);

if (!$this->container->hasParameter($default)) {
throw new RuntimeException(sprintf('Invalid env fallback in "default:%s": parameter "%s" not found.', $name, $default));
}

try {
return $getEnv($next);
} catch (EnvNotFoundException $e) {
return $this->container->getParameter($default);
}
}

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));
throw new EnvNotFoundException(sprintf('File "%s" not found (resolved from "%s").', $file, $name));
}

return file_get_contents($file);
Expand All @@ -94,7 +115,7 @@ public function getEnv($prefix, $name, \Closure $getEnv)
$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($name);
throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name));
}

if (null === $env = $this->container->getParameter("env($name)")) {
Expand Down
Expand Up @@ -18,8 +18,4 @@
*/
class EnvNotFoundException extends InvalidArgumentException
{
public function __construct(string $name)
{
parent::__construct(sprintf('Environment variable not found: "%s".', $name));
}
}
Expand Up @@ -40,6 +40,7 @@ public function testSimpleProcessor()
'json' => array('array'),
'key' => array('bool', 'int', 'float', 'string', 'array'),
'resolve' => array('string'),
'default' => array('bool', 'int', 'float', 'string', 'array'),
'string' => array('string'),
);

Expand Down
Expand Up @@ -439,6 +439,28 @@ public function testDumpedCsvEnvParameters()
$this->assertSame(array('foo', 'bar'), $container->getParameter('hello'));
}

public function testDumpedDefaultEnvParameters()
{
$container = new ContainerBuilder();
$container->setParameter('fallback_param', 'baz');
$container->setParameter('fallback_env', '%env(foobar)%');
$container->setParameter('env(foobar)', 'foobaz');
$container->setParameter('env(foo)', '{"foo": "bar"}');
$container->setParameter('hello', '%env(default:fallback_param:bar)%');
$container->setParameter('hello-bar', '%env(default:fallback_env:key:baz:json:foo)%');
$container->compile();

$dumper = new PhpDumper($container);
$dumper->dump();

$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_default_env.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_DefaultParameters')));

require self::$fixturesPath.'/php/services_default_env.php';
$container = new \Symfony_DI_PhpDumper_Test_DefaultParameters();
$this->assertSame('baz', $container->getParameter('hello'));
$this->assertSame('foobaz', $container->getParameter('hello-bar'));
}

public function testDumpedJsonEnvParameters()
{
$container = new ContainerBuilder();
Expand Down
Expand Up @@ -317,7 +317,7 @@ public function testGetEnvUnknown()

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid configuration: env var "key:foo" does not contain a key specifier.
* @expectedExceptionMessage Invalid env "key:foo": a key specifier should be provided.
*/
public function testGetEnvKeyInvalidKey()
{
Expand Down Expand Up @@ -355,7 +355,7 @@ public function noArrayValues()
}

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedException \Symfony\Component\DependencyInjection\Exception\EnvNotFoundException
* @expectedExceptionMessage Key "index" not found in
* @dataProvider invalidArrayValues
*/
Expand Down
@@ -0,0 +1,130 @@
<?php

use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;

/**
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class Symfony_DI_PhpDumper_Test_DefaultParameters extends Container
{
private $parameters;
private $targetDirs = array();

public function __construct()
{
$this->parameters = $this->getDefaultParameters();

$this->services = $this->privates = array();

$this->aliases = array();
}

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 array(
'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 = array(
'fallback_env' => false,
'hello' => false,
'hello-bar' => 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 'fallback_env': $value = $this->getEnv('foobar'); break;
case 'hello': $value = $this->getEnv('default:fallback_param:bar'); break;
case 'hello-bar': $value = $this->getEnv('default:fallback_env:key:baz:json: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(
'fallback_param' => 'baz',
'env(foobar)' => 'foobaz',
'env(foo)' => '{"foo": "bar"}',
);
}
}

0 comments on commit 67be665

Please sign in to comment.