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

Skip to content

Commit 465f5ab

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

File tree

10 files changed

+347
-19
lines changed

10 files changed

+347
-19
lines changed

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 43 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,45 @@ 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 variable to fetch from the environment
381+
*
382+
* @return string|null The value of the environment variable or null when not found
383+
*/
384+
protected function getEnvironmentVariable($name)
385+
{
386+
if (isset($_ENV[$name])) {
387+
return $_ENV[$name];
388+
}
389+
if (false !== $env = getenv($name)) {
390+
return $env;
391+
}
392+
}
393+
394+
/**
395+
* Adds caching on top of the getEnvironmentVariable() method.
396+
*
397+
* @throws EnvNotFoundException When the env var is not found and has no default value
398+
*
399+
* @internal
400+
*/
401+
protected function getEnv($name)
402+
{
403+
if (isset($this->envCache[$name]) || array_key_exists($name, $this->envCache)) {
404+
return $this->envCache[$name];
405+
}
406+
if (null !== $env = $this->getEnvironmentVariable($name)) {
407+
return $this->envCache[$name] = $env;
408+
}
409+
if (!$this->hasParameter("env($name)")) {
410+
throw new EnvNotFoundException($name);
411+
}
412+
413+
return $this->envCache[$name] = $this->getParameter("env($name)");
414+
}
415+
375416
private function __clone()
376417
{
377418
}

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+
}

0 commit comments

Comments
 (0)