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

Skip to content

Commit f7e0a54

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

File tree

10 files changed

+335
-19
lines changed

10 files changed

+335
-19
lines changed

src/Symfony/Component/DependencyInjection/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
}

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
/**
@@ -384,7 +386,7 @@ private function addServiceInstance($id, $definition)
384386

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

387-
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)) {
389+
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)) {
388390
throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
389391
}
390392

@@ -539,7 +541,7 @@ private function addServiceConfigurator($id, $definition, $variableName = 'insta
539541

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

@@ -572,6 +574,7 @@ private function addService($id, $definition)
572574
if ($definition->isSynthetic()) {
573575
$return[] = '@throws RuntimeException always since this service is expected to be injected dynamically';
574576
} elseif ($class = $definition->getClass()) {
577+
$class = $this->reverseEnvPlaceholders($class);
575578
$return[] = sprintf('@return %s A %s instance', 0 === strpos($class, '%') ? 'object' : '\\'.ltrim($class, '\\'), ltrim($class, '\\'));
576579
} elseif ($definition->getFactory()) {
577580
$factory = $definition->getFactory();
@@ -595,6 +598,7 @@ private function addService($id, $definition)
595598
}
596599

597600
$return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return));
601+
$return = $this->reverseEnvPlaceholders($return);
598602

599603
$doc = '';
600604
if ($definition->isShared()) {
@@ -654,7 +658,7 @@ private function addService($id, $definition)
654658
$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);
655659
} else {
656660
if ($definition->isDeprecated()) {
657-
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", var_export($definition->getDeprecationMessage($id), true));
661+
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
658662
}
659663

660664
$code .=
@@ -720,7 +724,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation,
720724

721725
$class = $this->dumpValue($callable[0]);
722726
// If the class is a string we can optimize call_user_func away
723-
if (strpos($class, "'") === 0) {
727+
if (0 === strpos($class, "'") && false === strpos($class, '$')) {
724728
if ("''" === $class) {
725729
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));
726730
}
@@ -735,7 +739,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation,
735739
return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : '');
736740
}
737741

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

741745
if (false !== strpos($class, '$')) {
@@ -904,7 +908,7 @@ private function addMethodMap()
904908
$code = " \$this->methodMap = array(\n";
905909
ksort($definitions);
906910
foreach ($definitions as $id => $definition) {
907-
$code .= ' '.var_export($id, true).' => '.var_export($this->generateMethodName($id), true).",\n";
911+
$code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n";
908912
}
909913

910914
return $code." );\n";
@@ -925,7 +929,7 @@ private function addPrivateServices()
925929
ksort($definitions);
926930
foreach ($definitions as $id => $definition) {
927931
if (!$definition->isPublic()) {
928-
$code .= ' '.var_export($id, true)." => true,\n";
932+
$code .= ' '.$this->export($id)." => true,\n";
929933
}
930934
}
931935

@@ -962,7 +966,7 @@ private function addAliases()
962966
while (isset($aliases[$id])) {
963967
$id = (string) $aliases[$id];
964968
}
965-
$code .= ' '.var_export($alias, true).' => '.var_export($id, true).",\n";
969+
$code .= ' '.$this->export($alias).' => '.$this->export($id).",\n";
966970
}
967971

968972
return $code." );\n";
@@ -1081,7 +1085,7 @@ private function exportParameters($parameters, $path = '', $indent = 12)
10811085
$value = $this->export($value);
10821086
}
10831087

1084-
$php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value);
1088+
$php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value);
10851089
}
10861090

10871091
return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 4));
@@ -1283,7 +1287,7 @@ private function dumpValue($value, $interpolate = true)
12831287
$factory = $value->getFactory();
12841288

12851289
if (is_string($factory)) {
1286-
return sprintf('\\%s(%s)', $factory, implode(', ', $arguments));
1290+
return sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory)), implode(', ', $arguments));
12871291
}
12881292

12891293
if (is_array($factory)) {
@@ -1358,7 +1362,7 @@ private function dumpValue($value, $interpolate = true)
13581362
private function dumpLiteralClass($class)
13591363
{
13601364
if (false !== strpos($class, '$')) {
1361-
throw new RuntimeException('Cannot dump definitions which have a variable class name.');
1365+
return sprintf('${($_ = %s) && false ?: "_"}', $class);
13621366
}
13631367
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)) {
13641368
throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s)', $class ?: 'n/a'));
@@ -1529,9 +1533,9 @@ private function exportTargetDirs()
15291533
private function export($value)
15301534
{
15311535
if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) {
1532-
$prefix = $matches[0][1] ? var_export(substr($value, 0, $matches[0][1]), true).'.' : '';
1536+
$prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1])).'.' : '';
15331537
$suffix = $matches[0][1] + strlen($matches[0][0]);
1534-
$suffix = isset($value[$suffix]) ? '.'.var_export(substr($value, $suffix), true) : '';
1538+
$suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix)) : '';
15351539
$dirname = '__DIR__';
15361540

15371541
if (0 < $offset = 1 + $this->targetDirMaxMatches - count($matches)) {
@@ -1545,6 +1549,34 @@ private function export($value)
15451549
return $dirname;
15461550
}
15471551

1548-
return var_export($value, true);
1552+
return $this->doExport($value);
1553+
}
1554+
1555+
private function doExport($value)
1556+
{
1557+
$export = var_export($value, true);
1558+
1559+
if ("'" === $export[0] && $this->envPlaceholders && false !== strpos($export, 'env_')) {
1560+
foreach ($this->envPlaceholders as $env => $placeholder) {
1561+
$export = str_replace($placeholder, "'.\$this->getEnv('$env').'", $export);
1562+
}
1563+
if ("'" === $export[1]) {
1564+
$export = substr($export, 3);
1565+
}
1566+
if (".''" === substr($export, -3)) {
1567+
$export = substr($export, 0, -3);
1568+
}
1569+
}
1570+
1571+
return $export;
1572+
}
1573+
1574+
private function reverseEnvPlaceholders($string)
1575+
{
1576+
foreach ($this->envPlaceholders as $env => $placeholder) {
1577+
$string = str_replace($placeholder, "%env($env)%", $string);
1578+
}
1579+
1580+
return $string;
15491581
}
15501582
}
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)