8000 feature #19681 [DI] Allow injecting ENV parameters at runtime using %… · symfony/dependency-injection@fc62b86 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit fc62b86

Browse files
committed
feature #19681 [DI] Allow injecting ENV parameters at runtime using %env(MY_ENV_VAR)% (nicolas-grekas)
This PR was merged into the 3.2-dev branch. Discussion ---------- [DI] Allow injecting ENV parameters at runtime using %env(MY_ENV_VAR)% | Q | A | ------------- | --- | Branch? | master | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #10138, #7555, #16403, #18155 | License | MIT | Doc PR | symfony/symfony-docs#6918 This is an alternative approach to #18155 for injecting env vars into container configurations. With this PR, anywhere parameters are allowed, one can use `%env(ENV_VAR)%` to inject a dynamic env var. Additionally, if one sets a value to such parameters in e.g. the `parameter.yml` file (`env(ENV_VAR): foo`), this value will be used as a default value when the env var is not defined. If no default value is specified, an `EnvVarNotFoundException` will be thrown at runtime. Unlike previous attempts that also used parameters (#16403), the implementation is compatible with DI extensions: before being dumped, env vars are resolved to uniquely identifiable string placeholders that can get through DI extensions manipulations. When dumped, these unique placeholders are replaced by dynamic calls to a getEnv method.. ping @magnusnordlander @dzuelke @fabpot Commits ------- bac2132 [DI] Allow injecting ENV parameters at runtime using %env(MY_ENV_VAR)% syntax
2 parents 18e66c6 + abf7436 commit fc62b86

18 files changed

+664
-42
lines changed

Compiler/Compiler.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
1516

1617
/**
1718
* This class is used to remove circular dependencies between individual passes.
@@ -108,8 +109,29 @@ public function getLog()
108109
*/
109110
public function compile(ContainerBuilder $container)
110111
{
111-
foreach ($this->passConfig->getPasses() as $pass) {
112-
$pass->process($container);
112+
try {
113+
foreach ($this->passConfig->getPasses() as $pass) {
114+
$pass->process($container);
115+
}
116+
} catch (\Exception $e) {
117+
$usedEnvs = array();
118+
$prev = $e;
119+
120+
do {
121+
$msg = $prev->getMessage();
122+
123+
if ($msg !== $resolvedMsg = $container->resolveEnvPlaceholders($msg, null, $usedEnvs)) {
124+
$r = new \ReflectionProperty($prev, 'message');
125+
$r->setAccessible(true);
126+
$r->setValue($prev, $resolvedMsg);
127+
}
128+
} while ($prev = $prev->getPrevious());
129+
130+
if ($usedEnvs) {
131+
$e = new EnvParameterException($usedEnvs, $e);
132+
}
133+
134+
throw $e;
113135
}
114136
}
115137
}

Container.php

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111

1212
namespace Symfony\Component\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
1415
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1516
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1617
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
1718
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
18-
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
19+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
1920
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
2021

2122
/**
@@ -70,13 +71,14 @@ class Container implements ResettableContainerInterface
7071
protected $loading = array();
7172

7273
private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_');
74+
private $envCache = array();
7375

7476
/**
7577
* @param ParameterBagInterface $parameterBag A ParameterBagInterface instance
7678
*/
7779
public function __construct(ParameterBagInterface $parameterBag = null)
7880
{
79-
$this->parameterBag = $parameterBag ?: new ParameterBag();
81+
$this->parameterBag = $parameterBag ?: new EnvPlaceholderParameterBag();
8082
}
8183

8284
/**
@@ -373,6 +375,33 @@ public static function underscore($id)
373375
return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id)));
374376
}
375377

378+
/**
379+
* Fetches a variable from the environment.
380+
*
381+
* @param string The name of the environment variable
382+
*
383+
* @return scalar The value to use for the provided environment variable name
384+
*
385+
* @throws EnvNotFoundException When the environment variable is not found and has no default value
386+
*/
387+
protected function getEnv($name)
388+
{
389+
if (isset($this->envCache[$name]) || array_key_exists($name, $this->envCache)) {
390+
return $this->envCache[$name];
391+
}
392+
if (isset($_ENV[$name])) {
393+
return $this->envCache[$name] = $_ENV[$name];
394+
}
395+
if (false !== $env = getenv($name)) {
396+
return $this->envCache[$name] = $env;
397+
}
398+
if (!$this->hasParameter("env($name)")) {
399+
throw new EnvNotFoundException($name);
400+
}
401+
402+
return $this->envCache[$name] = $this->getParameter("env($name)");
403+
}
404+
376405
private function __clone()
377406
{
378407
}

ContainerBuilder.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
2222
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
2323
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
24+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
2425
use Symfony\Component\Config\Resource\FileResource;
2526
use Symfony\Component\Config\Resource\ResourceInterface;
2627
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
@@ -89,6 +90,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
8990
*/
9091
private $usedTags = array();
9192

93+
/**
94+
* @var string[][] A map of env var names to their placeholders
95+
*/
96+
private $envPlaceholders = array();
97+
98+
/**
99+
* @var int[] A map of env vars to their resolution counter.
100+
*/
101+
private $envCounters = array();
102+
92103
private $compiled = false;
93104

94105
/**
@@ -482,6 +493,18 @@ public function merge(ContainerBuilder $container)
482493

483494
$this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
484495
}
496+
497+
if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) {
498+
$this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag());
499+
}
500+
501+
foreach ($container->envCounters as $env => $count) {
502+
if (!isset($this->envCounters[$env])) {
503+
$this->envCounters[$env] = $count;
504+
} else {
505+
$this->envCounters[$env] += $count;
506+
}
507+
}
485508
}
486509

487510
/**
@@ -552,8 +575,11 @@ public function compile()
552575
}
553576

554577
$this->extensionConfigs = array();
578+
$bag = $this->getParameterBag();
555579

556580
parent::compile();
581+
582+
$this->envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : array();
557583
}
558584

559585
/**
@@ -996,6 +1022,56 @@ public function getExpressionLanguageProviders()
9961022
return $this->expressionLanguageProviders;
9971023
}
9981024

1025+
/**
1026+
* Resolves env parameter placeholders in a string.
1027+
*
1028+
* @param string $string The string to resolve
1029+
* @param string|null $format A sprintf() format to use as replacement for env placeholders or null to use the default parameter format
1030+
* @param array &$usedEnvs Env vars found while resolving are added to this array
1031+
*
1032+
* @return string The string with env parameters resolved
1033+
*/
1034+
public function resolveEnvPlaceholders($string, $format = null, array &$usedEnvs = null)
1035+
{
1036+
$bag = $this->getParameterBag();
1037+
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1038+
1039+
if (null === $format) {
1040+
$format = '%%env(%s)%%';
1041+
}
1042+
1043+
foreach ($envPlaceholders as $env => $placeholders) {
1044+
foreach ($placeholders as $placeholder) {
1045+
if (false !== stripos($string, $placeholder)) {
1046+
$string = str_ireplace($placeholder, sprintf($format, $env), $string);
1047+
$usedEnvs[$env] = $env;
1048+
$this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
1049+
}
1050+
}
1051+
}
1052+
1053+
return $string;
1054+
}
1055+
1056+
/**
1057+
* Get statistics about env usage.
1058+
*
1059+
* @return int[] The number of time each env vars has been resolved
1060+
*/
1061+
public function getEnvCounters()
1062+
{
1063+
$bag = $this->getParameterBag();
1064+
$envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1065+
1066+
foreach ($envPlaceholders as $env => $placeholders) {
1067+
if (!isset($this->envCounters[$env])) {
1068+
$this->envCounters[$env] = 0;
1069+
}
1070+
}
1071+
1072+
return $this->envCounters;
1073+
}
1074+
9991075
/**
10001076
* Returns the Service Conditionals.
10011077
*

Dumper/GraphvizDumper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public function dump(array $options = array())
8181
}
8282
}
8383

84-
return $this->startDot().$this->addNodes().$this->addEdges().$this->endDot();
84+
return $this->container->resolveEnvPlaceholders($this->startDot().$this->addNodes().$this->addEdges().$this->endDot(), '__ENV_%s__');
8585
}
8686

8787
/**

0 commit comments

Comments
 (0)
0