8000 [DI] Allow processing env vars · symfony/symfony@1f92e45 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1f92e45

Browse files
[DI] Allow processing env vars
1 parent 2fde094 commit 1f92e45

18 files changed

+977
-25
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use Symfony\Component\DependencyInjection\ContainerBuilder;
3737
use Symfony\Component\DependencyInjection\ContainerInterface;
3838
use Symfony\Component\DependencyInjection\Definition;
39+
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
3940
use Symfony\Component\DependencyInjection\Exception\LogicException;
4041
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
4142
use Symfony\Component\DependencyInjection\Reference;
@@ -283,6 +284,8 @@ public function load(array $configs, ContainerBuilder $container)
283284
->addTag('console.command');
284285
$container->registerForAutoconfiguration(ResourceCheckerInterface::class)
285286
->addTag('config_cache.resource_checker');
287+
$container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
288+
->addTag('container.env_var_processor');
286289
$container->registerForAutoconfiguration(ServiceSubscriberInterface::class)
287290
->addTag('container.service_subscriber');
288291
$container->registerForAutoconfiguration(ArgumentValueResolverInterface::class)

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* added `EnvVarProcessorInterface` and corresponding "container.env_var_processor" tag for processing env vars
78
* added support for ignore-on-uninitialized references
89
* deprecated service auto-registration while autowiring
910
* deprecated the ability to check for the initialization of a private service with the `Container::initialized()` method

src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function __construct()
4343
100 => array(
4444
$resolveClassPass = new ResolveClassPass(),
4545
new ResolveInstanceofConditionalsPass(),
46+
new RegisterEnvVarProcessorsPass(),
4647
),
4748
);
4849

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
17+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
19+
use Symfony\Component\DependencyInjection\ServiceLocator;
20+
use Symfony\Component\DependencyInjection\Reference;
21+
22+
/**
23+
* Creates the container.env_var_processors_locator service.
24+
*
25+
* @author Nicolas Grekas <p@tchwork.com>
26+
*/
27+
class RegisterEnvVarProcessorsPass implements CompilerPassInterface
28+
{
29+
private static $allowedTypes = array('array', 'bool', 'float', 'int', 'string');
30+
31+
public function process(ContainerBuilder $container)
32+
{
33+
$bag = $container->getParameterBag();
34+
$types = array();
35+
$processors = array();
36+
foreach ($container->findTaggedServiceIds('container.env_var_processor') as $id => $tags) {
37+
foreach ($tags as $attr) {
38+
if (!$r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass())) {
39+
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
40+
} elseif (!$r->isSubclassOf(EnvVarProcessorInterface::class)) {
41+
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
42+
}
43+
foreach ($class::getProvidedTypes() as $prefix => $type) {
44+
$processors[$prefix] = new ServiceClosureArgument(new Reference($id));
45+
$types[$prefix] = self::validateProvidedTypes($type, $class);
46+
}
47+
}
48+
}
49+
50+
if ($processors) {
51+
if ($bag instanceof EnvPlaceholderParameterBag) {
52+
$bag->setProvidedTypes($types);
53+
}
54+
$container->register('container.env_var_processors_locator', ServiceLocator::class)
55+
->setArguments(array($processors))
56+
;
57+
}
58+
}
59+
60+
private static function validateProvidedTypes($types, $class)
61+
{
62+
$types = explode('|', $types);
63+
64+
foreach ($types as $type) {
65+
if (!in_array($type, self::$allowedTypes)) {
66+
throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::$allowedTypes)));
67+
}
68+
}
69+
70+
return $types;
71+
}
72+
}

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
1515
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
1617
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1718
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
1819
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
@@ -48,6 +49,7 @@ class Container implements ResettableContainerInterface
4849
protected $methodMap = array();
4950
protected $aliases = array();
5051
protected $loading = array();
52+
protected $resolving = array();
5153

5254
/**
5355
* @internal
@@ -62,6 +64,7 @@ class Container implements ResettableContainerInterface
6264
private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_');
6365
private $envCache = array();
6466
private $compiled = false;
67+
private $getEnv;
6568

6669
/**
6770
* @param ParameterBagInterface $parameterBag A ParameterBagInterface instance
@@ -438,23 +441,37 @@ protected function load($file)
438441
*/
439442
protected function getEnv($name)
440443
{
444+
if (isset($this->resolving[$envName = "env($name)"])) {
445+
throw new ParameterCircularReferenceException(array_keys($this->resolving));
446+
}
441447
if (isset($this->envCache[$name]) || array_key_exists($name, $this->envCache)) {
442448
return $this->envCache[$name];
443449
}
444-
if (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
445-
return $this->envCache[$name] = $_SERVER[$name];
446-
}
447-
if (isset($_ENV[$name])) {
448-
return $this->envCache[$name] = $_ENV[$name];
450+
if (!$this->has($id = 'container.env_var_processors_locator')) {
451+
$this->set($id, new ServiceLocator(array()));
449452
}
450-
if (false !== ($env = getenv($name)) && null !== $env) { // null is a possible value because of thread safety issues
451-
return $this->envCache[$name] = $env;
453+
if (!$this->getEnv) {
454+
$this->getEnv = new \ReflectionMethod($this, __FUNCTION__);
455+
$this->getEnv->setAccessible(true);
456+
$this->getEnv = $this->getEnv->getClosure($this);
452457
}
453-
if (!$this->hasParameter("env($name)")) {
454-
throw new EnvNotFoundException($name);
458+
$processors = $this->get($id);
459+
460+
if (false !== $i = strpos($name, ':')) {
461+
$prefix = substr($name, 0, $i);
462+
$localName = substr($name, 1 + $i);
463+
} else {
464+
$prefix = 'string';
465+
$localName = $name;
455466
}
467+
$processor = $processors->has($prefix) ? $processors->get($prefix) : new EnvVarProcessor($this);
456468

457-
return $this->envCache[$name] = $this->getParameter("env($name)");
469+
$this->resolving[$envName] = true;
470+
try {
471+
return $this->envCache[$name] = $processor->getEnv($prefix, $localName, $this->getEnv);
472+
} finally {
473+
unset($this->resolving[$envNam F438 e]);
474+
}
458475
}
459476

460477
/**

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,20 +1472,26 @@ public static function hash($value)
14721472
protected function getEnv($name)
14731473
{
14741474
$value = parent::getEnv($name);
1475+
$bag = $this->getParameterBag();
14751476

1476-
if (!is_string($value) || !$this->getParameterBag() instanceof EnvPlaceholderParameterBag) {
1477+
if (!is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) {
14771478
return $value;
14781479
}
14791480

1480-
foreach ($this->getParameterBag()->getEnvPlaceholders() as $env => $placeholders) {
1481+
foreach ($bag->getEnvPlaceholders() as $env => $placeholders) {
14811482
if (isset($placeholders[$value])) {
1482-
$bag = new ParameterBag($this->getParameterBag()->all());
1483+
$bag = new ParameterBag($bag->all());
14831484

14841485
return $bag->unescapeValue($bag->get("env($name)"));
14851486
}
14861487
}
14871488

1488-
return $value;
1489+
$this->resolving["env($name)"] = true;
1490+
try {
1491+
return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true));
1492+
} finally {
1493+
unset($this->resolving["env($name)"]);
1494+
}
14891495
}
14901496

14911497
/**

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ private function addDefaultParametersMethod()
11121112
$export = $this->exportParameters(array($value));
11131113
$export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2);
11141114

1115-
if (preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $export[1])) {
1115+
if (preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $export[1])) {
11161116
$dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]);
11171117
} else {
11181118
$php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
@@ -1685,7 +1685,7 @@ private function dumpParameter($name)
16851685
return $dumpedValue;
16861686
}
16871687

1688-
if (!preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) {
1688+
if (!preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) {
16891689
return sprintf("\$this->parameters['%s']", $name);
16901690
}
16911691
}
@@ -1880,13 +1880,16 @@ private function doExport($value)
18801880
{
18811881
$export = var_export($value, true);
18821882

1883-
if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('%s').'")) {
1883+
if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) {
18841884
$export = $resolvedExport;
1885-
if ("'" === $export[1]) {
1886-
$export = substr($export, 3);
1887-
}
18881885
if (".''" === substr($export, -3)) {
18891886
$export = substr($export, 0, -3);
1887+
if ("'" === $export[1]) {
1888+
$export = substr_replace($export, '', 18, 7);
1889+
}
1890+
}
1891+
if ("'" === $export[1]) {
1892+
$export = substr($export, 3);
18901893
}
18911894
}
18921895

0 commit comments

Comments
 (0)
0