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

Skip to content

Commit 95bb67b

Browse files
feature #23888 [DI] Validate env vars in config (ro0NL)
This PR was squashed before being merged into the 4.1-dev branch (closes #23888). Discussion ---------- [DI] Validate env vars in config | Q | A | ------------- | --- | Branch? | 4.1/master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #22151, #25868 | License | MIT | Doc PR | symfony/symfony-docs#8382 This PR registers the env placeholders in `Config\BaseNode` with its default value or an empty string. It doesnt request real env vars during compilation, What it does is if a config value exactly matches a env placeholder, we validate/normalize the default value/empty string but we keep returning the env placeholder as usual. If a placeholder occurs in the middle of a string it also proceeds as usual. The latter to me is OK as you need to expect any string value during runtime anyway, including the empty string. Commits ------- 2c74fbc [DI] Validate env vars in config
2 parents 01679ff + 65dc7c8 commit 95bb67b

7 files changed

+412
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* added PSR-11 `ContainerBagInterface` and its `ContainerBag` implementation to access parameters as-a-service
99
* added support for service's decorators autowiring
1010
* deprecated the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods
11+
* environment variables are validated when used in extension configuration
1112

1213
4.0.0
1314
-----

Compiler/MergeExtensionConfigurationPass.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\Config\Definition\BaseNode;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Exception\LogicException;
1617
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
@@ -37,6 +38,7 @@ public function process(ContainerBuilder $container)
3738
$definitions = $container->getDefinitions();
3839
$aliases = $container->getAliases();
3940
$exprLangProviders = $container->getExpressionLanguageProviders();
41+
$configAvailable = class_exists(BaseNode::class);
4042

4143
foreach ($container->getExtensions() as $extension) {
4244
if ($extension instanceof PrependExtensionInterface) {
@@ -53,6 +55,9 @@ public function process(ContainerBuilder $container)
5355
if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) {
5456
// create a dedicated bag so that we can track env vars per-extension
5557
$resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag);
58+
if ($configAvailable) {
59+
BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix());
60+
}
5661
}
5762
$config = $resolvingBag->resolveValue($config);
5863

@@ -75,6 +80,10 @@ public function process(ContainerBuilder $container)
7580
}
7681

7782
throw $e;
83+
} finally {
84+
if ($configAvailable) {
85+
BaseNode::resetPlaceholders();
86+
}
7887
}
7988

8089
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
@@ -132,6 +141,11 @@ public function getEnvPlaceholders()
132141
{
133142
return null !== $this->processedEnvPlaceholders ? $this->processedEnvPlaceholders : parent::getEnvPlaceholders();
134143
}
144+
145+
public function getUnusedEnvPlaceholders(): array
146+
{
147+
return null === $this->processedEnvPlaceholders ? array() : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders);
148+
}
135149
}
136150

137151
/**

Compiler/PassConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function __construct()
4949
);
5050

5151
$this->optimizationPasses = array(array(
52+
new ValidateEnvPlaceholdersPass(),
5253
new ResolveChildDefinitionsPass(),
5354
new ServiceLocatorTagPass(),
5455
new DecoratorServicePass(),
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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\Compiler;
13+
14+
use Symfony\Component\Config\Definition\BaseNode;
15+
use Symfony\Component\Config\Definition\Processor;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Exception\LogicException;
18+
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
19+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
20+
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
21+
22+
/**
23+
* Validates environment variable placeholders used in extension configuration with dummy values.
24+
*
25+
* @author Roland Franssen <[email protected]>
26+
*/
27+
class ValidateEnvPlaceholdersPass implements CompilerPassInterface
28+
{
29+
private static $typeFixtures = array('array' => array(), 'bool' => false, 'float' => 0.0, 'int' => 0, 'string' => '');
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function process(ContainerBuilder $container)
35+
{
36+
if (!class_exists(BaseNode::class) || !$extensions = $container->getExtensions()) {
37+
return;
38+
}
39+
40+
$resolvingBag = $container->getParameterBag();
41+
if (!$resolvingBag instanceof EnvPlaceholderParameterBag) {
42+
return;
43+
}
44+
45+
$defaultBag = new ParameterBag($container->getParameterBag()->all());
46+
$envTypes = $resolvingBag->getProvidedTypes();
47+
try {
48+
foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) {
49+
$prefix = (false === $i = strpos($env, ':')) ? 'string' : substr($env, 0, $i);
50+
$types = $envTypes[$prefix] ?? array('string');
51+
$default = ($hasEnv = (false === $i && $defaultBag->has("env($env)"))) ? $defaultBag->get("env($env)") : null;
52+
53+
if (null !== $default && !in_array($type = self::getType($default), $types, true)) {
54+
throw new LogicException(sprintf('Invalid type for env parameter "env(%s)". Expected "%s", but got "%s".', $env, implode('", "', $types), $type));
55+
}
56+
57+
$values = array();
58+
foreach ($types as $type) {
59+
$values[$type] = $hasEnv ? $default : self::$typeFixtures[$type] ?? null;
60+
}
61+
foreach ($placeholders as $placeholder) {
62+
BaseNode::setPlaceholder($placeholder, $values);
63+
}
64+
}
65+
66+
$processor = new Processor();
67+
68+
foreach ($extensions as $name => $extension) {
69+
if (!$extension instanceof ConfigurationExtensionInterface || !$config = $container->getExtensionConfig($name)) {
70+
// this extension has no semantic configuration or was not called
71+
continue;
72+
}
73+
74+
$config = $resolvingBag->resolveValue($config);
75+
76+
if (null === $configuration = $extension->getConfiguration($config, $container)) {
77+
continue;
78+
}
79+
80+
$processor->processConfiguration($configuration, $config);
81+
}
82+
} finally {
83+
BaseNode::resetPlaceholders();
84+
}
85+
}
86+
87+
private static function getType($value): string
88+
{
89+
switch ($type = \gettype($value)) {
90+
case 'boolean':
91+
return 'bool';
92+
case 'double':
93+
return 'float';
94+
case 'integer':
95+
return 'int';
96+
}
97+
98+
return $type;
99+
}
100+
}

ParameterBag/EnvPlaceholderParameterBag.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
*/
2020
class EnvPlaceholderParameterBag extends ParameterBag
2121
{
22+
private $envPlaceholderUniquePrefix;
2223
private $envPlaceholders = array();
24+
private $unusedEnvPlaceholders = array();
2325
private $providedTypes = array();
2426

2527
/**
@@ -35,6 +37,11 @@ public function get($name)
3537
return $placeholder; // return first result
3638
}
3739
}
40+
if (isset($this->unusedEnvPlaceholders[$env])) {
41+
foreach ($this->unusedEnvPlaceholders[$env] as $placeholder) {
42+
return $placeholder; // return first result
43+
}
44+
}
3845
if (!preg_match('/^(?:\w++:)*+\w++$/', $env)) {
3946
throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name));
4047
}
@@ -48,7 +55,7 @@ public function get($name)
4855
}
4956

5057
$uniqueName = md5($name.uniqid(mt_rand(), true));
51-
$placeholder = sprintf('env_%s_%s', str_replace(':', '_', $env), $uniqueName);
58+
$placeholder = sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), str_replace(':', '_', $env), $uniqueName);
5259
$this->envPlaceholders[$env][$placeholder] = $placeholder;
5360

5461
return $placeholder;
@@ -57,6 +64,14 @@ public function get($name)
5764
return parent::get($name);
5865
}
5966

67+
/**
68+
* Gets the common env placeholder prefix for env vars created by this bag.
69+
*/
70+
public function getEnvPlaceholderUniquePrefix(): string
71+
{
72+
return $this->envPlaceholderUniquePrefix ?? $this->envPlaceholderUniquePrefix = 'env_'.bin2hex(random_bytes(8));
73+
}
74+
6075
/**
6176
* Returns the map of env vars used in the resolved parameter values to their placeholders.
6277
*
@@ -67,6 +82,11 @@ public function getEnvPlaceholders()
6782
return $this->envPlaceholders;
6883
}
6984

85+
public function getUnusedEnvPlaceholders(): array
86+
{
87+
return $this->unusedEnvPlaceholders;
88+
}
89+
7090
/**
7191
* Merges the env placeholders of another EnvPlaceholderParameterBag.
7292
*/
@@ -79,6 +99,14 @@ public function mergeEnvPlaceholders(self $bag)
7999
$this->envPlaceholders[$env] += $placeholders;
80100
}
81101
}
102+
103+
if ($newUnusedPlaceholders = $bag->getUnusedEnvPlaceholders()) {
104+
$this->unusedEnvPlaceholders += $newUnusedPlaceholders;
105+
106+
foreach ($newUnusedPlaceholders as $env => $placeholders) {
107+
$this->unusedEnvPlaceholders[$env] += $placeholders;
108+
}
109+
}
82110
}
83111

84112
/**

0 commit comments

Comments
 (0)