8000 [DI] Validate env vars in config · symfony/symfony@2c74fbc · GitHub
[go: up one dir, main page]

Skip to content

Commit 2c74fbc

Browse files
ro0NLnicolas-grekas
authored andcommitted
[DI] Validate env vars in config
1 parent 61da487 commit 2c74fbc

18 files changed

+654
-7
lines changed

UPGRADE-4.1.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Console
1111
-------
1212

1313
* Deprecated the `setCrossingChar()` method in favor of the `setDefaultCrossingChar()` method in `TableStyle`.
14+
* The `Processor` class has been made final
1415

1516
DependencyInjection
1617
-------------------

UPGRADE-5.0.md

< 6D40 /div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Config
55
------
66

77
* Added the `getChildNodeDefinitions()` method to `ParentNodeDefinitionInterface`.
8+
* The `Processor` class has been made final
89

910
Console
1011
-------

src/Symfony/Component/Config/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 9E88 @@ CHANGELOG
66

77
* added `setPathSeparator` method to `NodeBuilder` class
88
* added third `$pathSeparator` constructor argument to `BaseNode`
9+
* the `Processor` class has been made final
910

1011
4.0.0
1112
-----

src/Symfony/Component/Config/Definition/ArrayNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,12 @@ protected function mergeValues($leftSide, $rightSide)
390390

391391
return $leftSide;
392392
}
393+
394+
/**
395+
* {@inheritdoc}
396+
*/
397+
protected function allowPlaceholders(): bool
398+
{
399+
return false;
400+
}
393401
}

src/Symfony/Component/Config/Definition/BaseNode.php

Lines changed: 189 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
1616
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1717
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
18+
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
1819

1920
/**
2021
* The base node class.
@@ -25,6 +26,9 @@ abstract class BaseNode implements NodeInterface
2526
{
2627
const DEFAULT_PATH_SEPARATOR = '.';
2728

29+
private static $placeholderUniquePrefix;
30+
private static $placeholders = array();
31+
2832
protected $name;
2933
protected $parent;
3034
protected $normalizationClosures = array();
@@ -36,6 +40,8 @@ abstract class BaseNode implements NodeInterface
3640
protected $attributes = array();
3741
protected $pathSeparator;
3842

43+
private $handlingPlaceholder;
44+
3945
/**
4046
* @throws \InvalidArgumentException if the name contains a period
4147
*/
@@ -50,6 +56,47 @@ public function __construct(?string $name, NodeInterface $parent = null, string
5056
$this->pathSeparator = $pathSeparator;
5157
}
5258

59+
/**
60+
* Register possible (dummy) values for a dynamic placeholder value.
61+
*
62+
* Matching configuration values will be processed with a provided value, one by one. After a provided value is
63+
* successfully processed the configuration value is returned as is, thus preserving the placeholder.
64+
*
65+
* @internal
66+
*/
67+
public static function setPlaceholder(string $placeholder, array $values): void
68+
{
69+
if (!$values) {
70+
throw new \InvalidArgumentException('At least one value must be provided.');
71+
}
72+
73+
self::$placeholders[$placeholder] = $values;
74+
}
75+
76+
/**
77+
* Sets a common prefix for dynamic placeholder values.
78+
*
79+
* Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
80+
* placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
81+
*
82+
* @internal
83+
*/
84+
public static function setPlaceholderUniquePrefix(string $prefix): void
85+
{
86+
self::$placeholderUniquePrefix = $prefix;
87+
}
88+
89+
/**
90+
* Resets all current placeholders available.
91+
*
92+
* @internal
93+
*/
94+
public static function resetPlaceholders(): void
95+
{
96+
self::$placeholderUniquePrefix = null;
97+
self::$placeholders = array();
98+
}
99+
53100
public function setAttribute($key, $value)
54101
{
55102
$this->attributes[$key] = $value;
@@ -249,8 +296,34 @@ final public function merge($leftSide, $rightSide)
249296
));
250297
}
251298

252-
$this->validateType($leftSide);
253-
$this->validateType($rightSide);
299+
if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) {
300+
foreach ($leftPlaceholders as $leftPlaceholder) {
301+
$this->handlingPlaceholder = $leftSide;
302+
try {
303+
$this->merge($leftPlaceholder, $rightSide);
304+
} finally {
305+
$this->handlingPlaceholder = null;
306+
}
307+
}
308+
309+
return $rightSide;
310+
}
311+
312+
if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) {
313+
foreach ($rightPlaceholders as $rightPlaceholder) {
314+
$this->handlingPlaceholder = $rightSide;
315+
try {
316+
$this->merge($leftSide, $rightPlaceholder);
317+
} finally {
318+
$this->handlingPlaceholder = null;
319+
}
320+
}
321+
322+
return $rightSide;
323+
}
324+
325+
$this->doValidateType($leftSide);
326+
$this->doValidateType($rightSide);
254327

255328
return $this->mergeValues($leftSide, $rightSide);
256329
}
@@ -267,6 +340,20 @@ final public function normalize($value)
267340
$value = $closure($value);
268341
}
269342

343+
// resolve placeholder value
344+
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
345+
foreach ($placeholders as $placeholder) {
346+
$this->handlingPlaceholder = $value;
347+
try {
348+
$this->normalize($placeholder);
349+
} finally {
350+
$this->handlingPlaceholder = null;
351+
}
352+
}
353+
354+
return $value;
355+
}
356+
270357
// replace value with their equivalent
271358
foreach ($this->equivalentValues as $data) {
272359
if ($data[0] === $value) {
@@ -275,7 +362,7 @@ final public function normalize($value)
275362
}
276363

277364
// validate type
278-
$this->validateType($value);
365+
$this->doValidateType($value);
279366

280367
// normalize value
281368
return $this->normalizeValue($value);
@@ -308,7 +395,20 @@ public function getParent()
308395
*/
309396
final public function finalize($value)
310397
{
311-
$this->validateType($value);
398+
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
399+
foreach ($placeholders as $placeholder) {
400+
$this->handlingPlaceholder = $value;
401+
try {
402+
$this->finalize($placeholder);
403+
} finally {
404+
$this->handlingPlaceholder = null;
405+
}
406+
}
407+
408+
return $value;
409+
}
410+
411+
$this->doValidateType($value);
312412

313413
$value = $this->finalizeValue($value);
314414

@@ -318,6 +418,10 @@ final public function finalize($value)
318418
try {
319419
$value = $closure($value);
320420
} catch (Exception $e) {
421+
if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) {
422+
continue;
423+
}
424+
321425
throw $e;
322426
} catch (\Exception $e) {
323427
throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e);
@@ -363,4 +467,85 @@ abstract protected function mergeValues($leftSide, $rightSide);
363467
* @return mixed The finalized value
364468
*/
365469
abstract protected function finalizeValue($value);
470+
471+
/**
472+
* Tests if placeholder values are allowed for this node.
473+
*/
474+
protected function allowPlaceholders(): bool
475+
{
476+
return true;
477+
}
478+
479+
/**
480+
* Gets allowed dynamic types for this node.
481+
*/
482+
protected function getValidPlaceholderTypes(): array
483+
{
484+
return array();
485+
}
486+
487+
private static function resolvePlaceholderValue($value)
488+
{
489+
if (\is_string($value)) {
490+
if (isset(self::$placeholders[$value])) {
491+
return self::$placeholders[$value];
492+
}
493+
494+
if (0 === strpos($value, self::$placeholderUniquePrefix)) {
495+
return array();
496+
}
497+
}
498+
499+
return $value;
500+
}
501+
502+
private static function getType($value): string
503+
{
504+
switch ($type = \gettype($value)) {
505+
case 'boolean':
506+
return 'bool';
507+
case 'double':
508+
return 'float';
509+
case 'integer':
510+
return 'int';
511+
}
512+
513+
return $type;
514+
}
515+
516+
private function doValidateType($value): void
517+
{
518+
if (null === $this->handlingPlaceholder || null === $value) {
519+
$this->validateType($value);
520+
521+
return;
522+
}
523+
524+
if (!$this->allowPlaceholders()) {
525+
$e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', get_class($this), $this->getPath()));
526+
$e->setPath($this->getPath());
527+
528+
throw $e;
529+
}
530+
531+
$knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]);
532+
$validTypes = $this->getValidPlaceholderTypes();
533+
534+
if (array_diff($knownTypes, $validTypes)) {
535+
$e = new InvalidTypeException(sprintf(
536+
'Invalid type for path "%s". Expected %s, but got %s.',
537+
$this->getPath(),
538+
1 === count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"',
539+
1 === count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"'
540+
));
541+
if ($hint = $this->getInfo()) {
542+
$e->addHint($hint);
543+
}
544+
$e->setPath($this->getPath());
545+
546+
throw $e;
547+
}
548+
549+
$this->validateType($value);
550+
}
366551
}

src/Symfony/Component/Config/Definition/BooleanNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,12 @@ protected function isValueEmpty($value)
4848
// a boolean value cannot be empty
4949
return false;
5050
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
protected function getValidPlaceholderTypes(): array
56+
{
57+
return array('bool');
58+
}
5159
}

src/Symfony/Component/Config/Definition/EnumNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,12 @@ protected function finalizeValue($value)
5555

5656
return $value;
5757
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
protected function allowPlaceholders(): bool
63+
{
64+
return false;
65+
}
5866
}

src/Symfony/Component/Config/Definition/FloatNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,12 @@ protected function validateType($value)
4040
throw $ex;
4141
}
4242
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
protected function getValidPlaceholderTypes(): array
48+
{
49+
return array('float');
50+
}
4351
}

src/Symfony/Component/Config/Definition/IntegerNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,12 @@ protected function validateType($value)
3535
throw $ex;
3636
}
3737
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
protected function getValidPlaceholderTypes(): array
43+
{
44+
return array('int');
45+
}
3846
}

src/Symfony/Component/Config/Definition/Processor.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* This class is the entry point for config normalization/merging/finalization.
1616
*
1717
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
18+
*
19+
* @final since version 4.1
1820
*/
1921
class Processor
2022
{

src/Symfony/Component/Config/Definition/ScalarNode.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,12 @@ protected function isValueEmpty($value)
5454
{
5555
return null === $value || '' === $value;
5656
}
57+
58+
/**
59+
* {@inheritdoc}
60+
*/
61+
protected function getValidPlaceholderTypes(): array
62+
{
63+
return array('bool', 'int', 'float', 'string');
64+
}
5765
}

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

0 commit comments

Comments
 (0)
0