-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Validate env vars in config #23888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
68ee20e
d33b1c8
e7b9656
1e59cf7
a90d34b
e5f02cd
c76adcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,7 @@ abstract class BaseNode implements NodeInterface | |
protected $attributes = array(); | ||
protected $pathSeparator; | ||
|
||
private $handlingPlaceholder = false; | ||
private $handlingPlaceholder; | ||
|
||
/** | ||
* @throws \InvalidArgumentException if the name contains a period | ||
|
@@ -289,47 +289,33 @@ final public function merge($leftSide, $rightSide) | |
} | ||
|
||
if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) { | ||
if (!$leftPlaceholders) { | ||
return $rightSide; | ||
} | ||
|
||
foreach ($leftPlaceholders as $leftPlaceholder) { | ||
$this->handlingPlaceholder = true; | ||
$this->handlingPlaceholder = $leftSide; | ||
try { | ||
$this->merge($leftPlaceholder, $rightSide); | ||
|
||
return $rightSide; | ||
} catch (InvalidConfigurationException $e) { | ||
} finally { | ||
$this->handlingPlaceholder = false; | ||
$this->handlingPlaceholder = null; | ||
} | ||
} | ||
|
||
throw $e; | ||
return $rightSide; | ||
} | ||
|
||
if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) { | ||
if (!$rightPlaceholders) { | ||
return $rightSide; | ||
} | ||
|
||
foreach ($rightPlaceholders as $rightPlaceholder) { | ||
$this->handlingPlaceholder = true; | ||
$this->handlingPlaceholder = $rightSide; | ||
try { | ||
$this->merge($leftSide, $rightPlaceholder); | ||
|
||
return $rightSide; | ||
} catch (InvalidConfigurationException $e) { | ||
} finally { | ||
$this->handlingPlaceholder = false; | ||
$this->handlingPlaceholder = null; | ||
} | ||
} | ||
|
||
throw $e; | ||
return $rightSide; | ||
} | ||
|
||
$this->validateType($leftSide); | ||
$this->validateType($rightSide); | ||
$this->doValidateType($leftSide); | ||
$this->doValidateType($rightSide); | ||
|
||
return $this->mergeValues($leftSide, $rightSide); | ||
} | ||
|
@@ -348,23 +334,16 @@ final public function normalize($value) | |
|
||
// resolve placeholder value | ||
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) { | ||
if (!$placeholders) { | ||
return $value; | ||
} | ||
|
||
foreach ($placeholders as $placeholder) { | ||
$this->handlingPlaceholder = true; | ||
$this->handlingPlaceholder = $value; | ||
try { | ||
$this->normalize($placeholder); | ||
|
||
return $value; | ||
} catch (InvalidConfigurationException $e) { | ||
} finally { | ||
$this->handlingPlaceholder = false; | ||
$this->handlingPlaceholder = null; | ||
} | ||
} | ||
|
||
throw $e; | ||
return $value; | ||
} | ||
|
||
// replace value with their equivalent | ||
|
@@ -375,7 +354,7 @@ final public function normalize($value) | |
} | ||
|
||
// validate type | ||
$this->validateType($value); | ||
$this->doValidateType($value); | ||
|
||
// normalize value | ||
return $this->normalizeValue($value); | ||
|
@@ -409,33 +388,19 @@ public function getParent() | |
final public function finalize($value) | ||
{ | ||
if ($value !== $placeholders = self::resolvePlaceholderValue($value)) { | ||
if (!$placeholders) { | ||
return $value; | ||
} | ||
|
||
foreach ($placeholders as $placeholder) { | ||
$this->handlingPlaceholder = true; | ||
$this->handlingPlaceholder = $value; | ||
try { | ||
$this->finalize($placeholder); | ||
|
||
return $value; | ||
} catch (InvalidConfigurationException $e) { | ||
} finally { | ||
$this->handlingPlaceholder = false; | ||
$this->handlingPlaceholder = null; | ||
} | ||
} | ||
|
||
throw $e; | ||
return $value; | ||
} | ||
|
||
$this->validateType($value); | ||
|
||
if ($this->handlingPlaceholder && !$this->allowPlaceholders()) { | ||
$e = new InvalidConfigurationException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', get_class($this), $this->getPath())); | ||
$e->setPath($this->getPath()); | ||
|
||
throw $e; | ||
} | ||
$this->doValidateType($value); | ||
|
||
$value = $this->finalizeValue($value); | ||
|
||
|
@@ -445,7 +410,7 @@ final public function finalize($value) | |
try { | ||
$value = $closure($value); | ||
} catch (Exception $e) { | ||
if ($e instanceof UnsetKeyException && $this->handlingPlaceholder) { | ||
if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) { | ||
continue; | ||
} | ||
|
||
|
@@ -503,6 +468,14 @@ protected function allowPlaceholders(): bool | |
return true; | ||
} | ||
|
||
/** | ||
* Get allowed dynamic types for this node. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gets |
||
*/ | ||
protected function getValidPlaceholderTypes(): array | ||
{ | ||
return array(); | ||
} | ||
|
||
private static function resolvePlaceholderValue($value) | ||
{ | ||
if (\is_string($value)) { | ||
|
@@ -517,4 +490,54 @@ private static function resolvePlaceholderValue($value) | |
|
||
return $value; | ||
} | ||
|
||
private static function getType($value): string | ||
{ | ||
switch ($type = gettype($value)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. \gettype |
||
case 'boolean': | ||
return 'bool'; | ||
case 'double': | ||
return 'float'; | ||
case 'integer': | ||
return 'int'; | ||
} | ||
|
||
return $type; | ||
} | ||
|
||
private function doValidateType($value): void | ||
{ | ||
if (null === $this->handlingPlaceholder || null === $value) { | ||
$this->validateType($value); | ||
|
||
return; | ||
} | ||
|
||
if (!$this->allowPlaceholders()) { | ||
$e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', get_class($this), $this->getPath())); | ||
$e->setPath($this->getPath()); | ||
|
||
throw $e; | ||
} | ||
|
||
$knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]); | ||
$validTypes = $this->getValidPlaceholderTypes(); | ||
|
||
if (array_diff($knownTypes, $validTypes)) { | ||
$e = new InvalidTypeException(sprintf( | ||
'Invalid type for path "%s". Expected %s, but got %s.', | ||
$this->getPath(), | ||
1 === count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"', | ||
1 === count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"' | ||
)); | ||
if ($hint = $this->getInfo()) { | ||
$e->addHint($hint); | ||
} | ||
$e->setPath($this->getPath()); | ||
|
||
throw $e; | ||
} | ||
|
||
$this->validateType($value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,22 @@ public function testEnvsAreValidatedInConfig() | |
'scalar_node' => '%env(NULLED)%', | ||
'int_node' => '%env(int:FOO)%', | ||
'float_node' => '%env(float:BAR)%', | ||
)); | ||
|
||
$this->doProcess($container); | ||
|
||
$this->assertSame($expected, $container->resolveEnvPlaceholders($ext->getConfig())); | ||
} | ||
|
||
/** | ||
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException | ||
* @expectedExceptionMessage Invalid type for path "env_extension.bool_node". Expected "bool", but got one of "bool", "int", "float", "string", "array". | ||
*/ | ||
public function testEnvsAreValidatedInConfigWithInvalidPlaceholder() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$container->registerExtension($ext = new EnvExtension()); | ||
$container->prependExtensionConfig('env_extension', $expected = array( | ||
'bool_node' => '%env(const:BAZ)%', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps this should be invalid 🤔 it can provide a bool, but also a string or so. so we need to be a bit more clear on what a placeholder value "means" in Config, that is; i think those must be valid for the current node type domain. A bool node only expect boolean dummy values for a placeholder. If it encounters a different type, the config is invalid. |
||
)); | ||
|
||
|
@@ -57,7 +73,7 @@ public function testEnvsAreValidatedInConfig() | |
|
||
/** | ||
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidTypeException | ||
* @expectedExceptionMessage Invalid type for path "env_extension.int_node". Expected int, but got array. | ||
* @expectedExceptionMessage Invalid type for path "env_extension.int_node". Expected "int", but got "array". | ||
*/ | ||
public function testInvalidEnvInConfig() | ||
{ | ||
|
@@ -91,7 +107,7 @@ public function testValidateEnvOnMerge() | |
$container = new ContainerBuilder(); | ||
$container->registerExtension($ext = new EnvExtension()); | ||
$container->prependExtensionConfig('env_extension', array( | ||
'int_node' => '%env(const:FOO)%', | ||
'int_node' => '%env(int:const:FOO)%', | ||
'bool_node' => true, | ||
)); | ||
$container->prependExtensionConfig('env_extension', array( | ||
|
@@ -103,7 +119,7 @@ public function testValidateEnvOnMerge() | |
$this->doProcess($container); | ||
|
||
$expected = array( | ||
'int_node' => '%env(const:FOO)%', | ||
'int_node' => '%env(int:const:FOO)%', | ||
'bool_node' => true, | ||
'scalar_node' => '%env(BAZ)%', | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case the value is a placeholder, I think we should preserve the value before the finalization (which will be the placeholder) and return this one instead. Otherwise, transformations done on the placeholder would break it. It should still be called though.
this advice should be challenged though, as it may depend on the usage of the finalization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's exactly what we do no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See https://github.com/symfony/symfony/pull/23888/files#diff-446b942cb6ccd9c1db2aaff25accc810R415