Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 64d613a

Browse files
[DI][HttpKernel] Allow injecting ENV parameters at runtime using %env(MY_ENV_VAR)% syntax
1 parent 94078a0 commit 64d613a

File tree

10 files changed

+315
-19
lines changed

10 files changed

+315
-19
lines changed

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 19 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,21 @@ 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+
protected function getEnv($name)
379+
{
380+
if (isset($this->envCache[$name]) || array_key_exists($name, $this->envCache)) {
381+
return $this->envCache[$name];
382+
}
383+
if (false !== $env = getenv($name)) {
384+
return $this->envCache[$name] = $env;
385+
}
386+
if (!$this->hasParameter("env($name)")) {
387+
throw new EnvNotFoundException($name);
388+
}
389+
390+
return $this->envCache[$name] = $this->getParameter("env($name)");
391+
}
392+
376393
private function __clone()
377394
{
378395
}

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 21 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,11 @@ 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+
9298
private $compiled = false;
9399

94100
/**
@@ -551,8 +557,11 @@ public function compile()
551557
}
552558

553559
$this->extensionConfigs = array();
560+
$bag = $this->getParameterBag();
554561

555562
parent::compile();
563+
564+
$this->envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : array();
556565
}
557566

558567
/**
@@ -995,6 +1004,18 @@ public function getExpressionLanguageProviders()
9951004
return $this->expressionLanguageProviders;
9961005
}
9971006

1007+
/**
1008+
* Returns the map of env vars used in the resolved parameter values to their placeholders.
1009+
*
1010+
* @return string[] A map of env var names to their placeholders
1011+
*/
1012+
public function getEnvPlaceholders()
1013+
{
1014+
$bag = $this->getParameterBag();
1015+
1016+
return $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
1017+
}
1018+
9981019
/**
9991020
* Returns the Service Conditionals.
10001021
*

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class PhpDumper extends Dumper
6060
private $docStar;
6161
private $serviceIdToMethodNameMap;
6262
private $usedMethodNames;
63+
private $envPlaceholders;
6364

6465
/**
6566
* @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
@@ -74,6 +75,7 @@ public function __construct(ContainerBuilder $container)
7475
parent::__construct($container);
7576

7677
$this->inlinedDefinitions = new \SplObjectStorage();
78+
$this->envPlaceholders = $container->getEnvPlaceholders();
7779
}
7880

7981
/**
@@ -383,7 +385,7 @@ private function addServiceInstance($id, $definition)
383385

384386
$class = $this->dumpValue($class);
385387

386-
if (0 === strpos($class, "'") && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
388+
if (0 === strpos($class, "'") && false === strpos($class, '$') && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
387389
throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
388390
}
389391

@@ -538,7 +540,7 @@ private function addServiceConfigurator($id, $definition, $variableName = 'insta
538540

539541
$class = $this->dumpValue($callable[0]);
540542
// If the class is a string we can optimize call_user_func away
541-
if (strpos($class, "'") === 0) {
543+
if (0 === strpos($class, "'") && false === strpos($class, '$')) {
542544
return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName);
543545
}
544546

@@ -571,6 +573,7 @@ private function addService($id, $definition)
571573
if ($definition->isSynthetic()) {
572574
$return[] = '@throws RuntimeException always since this service is expected to be injected dynamically';
573575
} elseif ($class = $definition->getClass()) {
576+
$class = $this->reverseEnvPlaceholders($class);
574577
$return[] = sprintf('@return %s A %s instance', 0 === strpos($class, '%') ? 'object' : '\\'.ltrim($class, '\\'), ltrim($class, '\\'));
575578
} elseif ($definition->getFactory()) {
576579
$factory = $definition->getFactory();
@@ -594,6 +597,7 @@ private function addService($id, $definition)
594597
}
595598

596599
$return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return));
600+
$return = $this->reverseEnvPlaceholders($return);
597601

598602
$doc = '';
599603
if ($definition->isShared()) {
@@ -653,7 +657,7 @@ private function addService($id, $definition)
653657
$code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id);
654658
} else {
655659
if ($definition->isDeprecated()) {
656-
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", var_export($definition->getDeprecationMessage($id), true));
660+
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
657661
}
658662

659663
$code .=
@@ -719,7 +723,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation,
719723

720724
$class = $this->dumpValue($callable[0]);
721725
// If the class is a string we can optimize call_user_func away
722-
if (strpos($class, "'") === 0) {
726+
if (0 === strpos($class, "'") && false === strpos($class, '$')) {
723727
if ("''" === $class) {
724728
throw new RuntimeException(sprintf('Cannot dump definition: The "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id));
725729
}
@@ -734,7 +738,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation,
734738
return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : '');
735739
}
736740

737-
return sprintf(" $return{$instantiation}\\%s(%s);\n", $callable, $arguments ? implode(', ', $arguments) : '');
741+
return sprintf(" $return{$instantiation}%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '');
738742
}
739743

740744
if (false !== strpos($class, '$')) {
@@ -883,7 +887,7 @@ private function addMethodMap()
883887
$code = " \$this->methodMap = array(\n";
884888
ksort($definitions);
885889
foreach ($definitions as $id => $definition) {
886-
$code .= ' '.var_export($id, true).' => '.var_export($this->generateMethodName($id), true).",\n";
890+
$code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n";
887891
}
888892

889893
return $code." );\n";
@@ -904,7 +908,7 @@ private function addPrivateServices()
904908
ksort($definitions);
905909
foreach ($definitions as $id => $definition) {
906910
if (!$definition->isPublic()) {
907-
$code .= ' '.var_export($id, true)." => true,\n";
911+
$code .= ' '.$this->export($id)." => true,\n";
908912
}
909913
}
910914

@@ -941,7 +945,7 @@ private function addAliases()
941945
while (isset($aliases[$id])) {
942946
$id = (string) $aliases[$id];
943947
}
944-
$code .= ' '.var_export($alias, true).' => '.var_export($id, true).",\n";
948+
$code .= ' '.$this->export($alias).' => '.$this->export($id).",\n";
945949
}
946950

947951
return $code." );\n";
@@ -1060,7 +1064,7 @@ private function exportParameters($parameters, $path = '', $indent = 12)
10601064
$value = $this->export($value);
10611065
}
10621066

1063-
$php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value);
1067+
$php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value);
10641068
}
10651069

10661070
return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 4));
@@ -1262,7 +1266,7 @@ private function dumpValue($value, $interpolate = true)
12621266
$factory = $value->getFactory();
12631267

12641268
if (is_string($factory)) {
1265-
return sprintf('\\%s(%s)', $factory, implode(', ', $arguments));
1269+
return sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory)), implode(', ', $arguments));
12661270
}
12671271

12681272
if (is_array($factory)) {
@@ -1337,7 +1341,7 @@ private function dumpValue($value, $interpolate = true)
13371341
private function dumpLiteralClass($class)
13381342
{
13391343
if (false !== strpos($class, '$')) {
1340-
throw new RuntimeException('Cannot dump definitions which have a variable class name.');
1344+
return sprintf('${($_ = %s) && false ?: "_"}', $class);
13411345
}
13421346
if (0 !== strpos($class, "'") || !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) {
13431347
throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s)', $class ?: 'n/a'));
@@ -1508,9 +1512,9 @@ private function exportTargetDirs()
15081512
private function export($value)
15091513
{
15101514
if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) {
1511-
$prefix = $matches[0][1] ? var_export(substr($value, 0, $matches[0][1]), true).'.' : '';
1515+
$prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1])).'.' : '';
15121516
$suffix = $matches[0][1] + strlen($matches[0][0]);
1513-
$suffix = isset($value[$suffix]) ? '.'.var_export(substr($value, $suffix), true) : '';
1517+
$suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix)) : '';
15141518
$dirname = '__DIR__';
15151519

15161520
if (0 < $offset = 1 + $this->targetDirMaxMatches - count($matches)) {
@@ -1524,6 +1528,34 @@ private function export($value)
15241528
return $dirname;
15251529
}
15261530

1527-
return var_export($value, true);
1531+
return $this->doExport($value);
1532+
}
1533+
1534+
private function doExport($value)
1535+
{
1536+
$export = var_export($value, true);
1537+
1538+
if ("'" === $export[0] && $this->envPlaceholders && false !== strpos($export, 'env_')) {
1539+
foreach ($this->envPlaceholders as $env => $placeholder) {
1540+
$export = str_replace($placeholder, "'.\$this->getEnv('$env').'", $export);
1541+
}
1542+
if ("'" === $export[1]) {
1543+
$export = substr($export, 3);
1544+
}
1545+
if (".''" === substr($export, -3)) {
1546+
$export = substr($export, 0, -3);
1547+
}
1548+
}
1549+
1550+
return $export;
1551+
}
1552+
1553+
private function reverseEnvPlaceholders($string)
1554+
{
1555+
foreach ($this->envPlaceholders as $env => $placeholder) {
1556+
$string = str_replace($placeholder, "%env($env)%", $string);
1557+
}
1558+
1559+
return $string;
15281560
}
15291561
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Exception;
13+
14+
/**
15+
* This exception is thrown when an environment variable is not found.
16+
*
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
class EnvNotFoundException extends InvalidArgumentException
20+
{
21+
public function __construct($name)
22+
{
23+
parent::__construct(sprintf('Environment variable not found: "%s".', $name));
24+
}
25+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\ParameterBag;
13+
14+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
15+
16+
/**
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
class EnvPlaceholderParameterBag extends ParameterBag
20+
{
21+
private $envPlaceholders = array();
22+
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function get($name)
27+
{
28+
if (0 === strpos($name, 'env(') && ')' === substr($name, -1) && 'env()' !== $name) {
29+
$env = substr($name, 4, -1);
30+
31+
if (isset($this->envPlaceholders[$env])) {
32+
return $this->envPlaceholders[$env];
33+
}
34+
if (!preg_match('/^[A-Z_]+$/i', $env)) {
35+
throw new InvalidArgumentException(sprintf('Invalid %s name: only characters "A-Z" and "_" are allowed.', $name));
36+
}
37+
38+
if ($this->has($name)) {
39+
$defaultValue = parent::get($name);
40+
41+
if (!is_scalar($defaultValue)) {
42+
throw new RuntimeException(sprintf('The default value of an env() parameter must be string, but "%s" given to "%s".', gettype($defaultValue), $name));
43+
}
44+
}
45+
46+
return $this->envPlaceholders[$env] = sprintf('env_%s_%s', $env, md5($name.uniqid(mt_rand(), true)));
47+
}
48+
49+
return parent::get($name);
50+
}
51+
52+
/**
53+
* Returns the map of env vars used in the resolved parameter values to their placeholders.
54+
*
55+
* @return string[] A map of env var names to their placeholders
56+
*/
57+
public function getEnvPlaceholders()
58+
{
59+
return $this->envPlaceholders;
60+
}
61+
}

src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,9 @@ public function resolveString($value, array $resolving = array())
196196
}
197197

198198
$resolving[$key] = true;
199+
$value = $this->get($match[1]);
199200

200-
return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving);
201+
return $this->resolved ? $value : $this->resolveValue($value, $resolving);
201202
}
202203

203204
return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($resolving, $value) {

0 commit comments

Comments
 (0)