Skip to content

Commit abf7436

Browse files
[DI] Allow injecting ENV parameters at runtime using %env(MY_ENV_VAR)% syntax
1 parent 8d9c1cf commit abf7436

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
/**
@@ -372,6 +374,33 @@ public static function underscore($id)
372374
return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id)));
373375
}
374376

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

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
/**
@@ -481,6 +492,18 @@ public function merge(ContainerBuilder $container)
481492

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

486509
/**
@@ -551,8 +574,11 @@ public function compile()
551574
}
552575

553576
$this->extensionConfigs = array();
577+
$bag = $this->getParameterBag();
554578

555579
parent::compile();
580+
581+
$this->envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : array();
556582
}
557583

558584
/**
@@ -995,6 +1021,56 @@ public function getExpressionLanguageProviders()
9951021
return $this->expressionLanguageProviders;
9961022
}
9971023

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

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)