Skip to content

[Config] Allow to always use config classes #51544

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

Draft
wants to merge 2 commits into
base: 7.3
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 55 additions & 108 deletions src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\BooleanNode;
use Symfony\Component\Config\Definition\Builder\ExprBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
Expand Down Expand Up @@ -127,24 +126,18 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
$class->addRequire($childClass);
$this->classes[] = $childClass;

$hasNormalizationClosures = $this->hasNormalizationClosures($node);
$comment = $this->getComment($node);
if ($hasNormalizationClosures) {
$comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment);
$comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
$comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
}
if ('' !== $comment) {
$comment = "/**\n$comment*/\n";
}
$comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment);
$comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
$comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
$comment = "/**\n$comment*/\n";

$property = $class->addProperty(
$node->getName(),
$this->getType($childClass->getFqcn(), $hasNormalizationClosures)
$childClass->getFqcn().'|scalar'
);
$nodeTypes = $this->getParameterTypes($node);
$body = $hasNormalizationClosures ? '
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
$body = '
COMMENTpublic function NAME(mixed $value = []): CLASS|static
{
if (!\is_array($value)) {
$this->_usedProperties[\'PROPERTY\'] = true;
Expand All @@ -160,26 +153,10 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
}

return $this->PROPERTY;
}' : '
COMMENTpublic function NAME(array $value = []): CLASS
{
if (null === $this->PROPERTY) {
$this->_usedProperties[\'PROPERTY\'] = true;
$this->PROPERTY = new CLASS($value);
} elseif (0 < \func_num_args()) {
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
}

return $this->PROPERTY;
}';
$class->addUse(InvalidConfigurationException::class);
$class->addMethod($node->getName(), $body, [
'COMMENT' => $comment,
'PROPERTY' => $property->getName(),
'CLASS' => $childClass->getFqcn(),
'PARAM_TYPE' => \in_array('mixed', $nodeTypes, true) ? 'mixed' : implode('|', $nodeTypes),
]);
$class->addMethod($node->getName(), $body, ['COMMENT' => $comment, 'PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);

$this->buildNode($node, $childClass, $this->getSubNamespace($childClass));
}
Expand Down Expand Up @@ -216,21 +193,19 @@ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuild
$methodName = $name;
$hasNormalizationClosures = $this->hasNormalizationClosures($node) || $this->hasNormalizationClosures($prototype);

$nodeParameterTypes = $this->getParameterTypes($node);
$prototypeParameterTypes = $this->getParameterTypes($prototype);
if (!$prototype instanceof ArrayNode || ($prototype instanceof PrototypedArrayNode && $prototype->getPrototype() instanceof ScalarNode)) {
$parameterType = $this->getParameterType($prototype);
if (null !== $parameterType || $prototype instanceof ScalarNode) {
$class->addUse(ParamConfigurator::class);
$property = $class->addProperty($node->getName());
if (null === $key = $node->getKeyAttribute()) {
// This is an array of values; don't use singular name
$nodeTypesWithoutArray = array_filter($nodeParameterTypes, static fn ($type) => 'array' !== $type);
$body = '
/**
* @param ParamConfigurator|list<ParamConfigurator|PROTOTYPE_TYPE>EXTRA_TYPE $value
* @param PHPDOC_TYPE $value
*
* @return $this
*/
public function NAME(PARAM_TYPE $value): static
public function NAME(TYPE $value): static
{
$this->_usedProperties[\'PROPERTY\'] = true;
$this->PROPERTY = $value;
Expand All @@ -240,9 +215,8 @@ public function NAME(PARAM_TYPE $value): static

$class->addMethod($node->getName(), $body, [
'PROPERTY' => $property->getName(),
'PROTOTYPE_TYPE' => implode('|', $prototypeParameterTypes),
'EXTRA_TYPE' => $nodeTypesWithoutArray ? '|'.implode('|', $nodeTypesWithoutArray) : '',
'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $nodeParameterTypes),
'TYPE' => $hasNormalizationClosures ? 'mixed' : 'ParamConfigurator|array',
'PHPDOC_TYPE' => $hasNormalizationClosures ? 'mixed' : sprintf('ParamConfigurator|list<ParamConfigurator|%s>', '' === $parameterType ? 'mixed' : $parameterType),
]);
} else {
$body = '
Expand All @@ -259,7 +233,7 @@ public function NAME(string $VAR, TYPE $VALUE): static

$class->addMethod($methodName, $body, [
'PROPERTY' => $property->getName(),
'TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : 'ParamConfigurator|'.implode('|', $prototypeParameterTypes),
'TYPE' => 'mixed',
'VAR' => '' === $key ? 'key' : $key,
'VALUE' => 'value' === $key ? 'data' : 'value',
]);
Expand All @@ -277,22 +251,18 @@ public function NAME(string $VAR, TYPE $VALUE): static

$property = $class->addProperty(
$node->getName(),
$this->getType($childClass->getFqcn().'[]', $hasNormalizationClosures)
$childClass->getFqcn().'[]|scalar'
);

$comment = $this->getComment($node);
if ($hasNormalizationClosures) {
$comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment);
$comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
$comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
}
if ('' !== $comment) {
$comment = "/**\n$comment*/\n";
}
$comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment);
$comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn());
$comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn());
$comment = "/**\n$comment*/\n";

if (null === $key = $node->getKeyAttribute()) {
$body = $hasNormalizationClosures ? '
COMMENTpublic function NAME(PARAM_TYPE $value = []): CLASS|static
$body = '
COMMENTpublic function NAME(mixed $value = []): CLASS|static
{
$this->_usedProperties[\'PROPERTY\'] = true;
if (!\is_array($value)) {
Expand All @@ -301,23 +271,12 @@ public function NAME(string $VAR, TYPE $VALUE): static
return $this;
}

return $this->PROPERTY[] = new CLASS($value);
}' : '
COMMENTpublic function NAME(array $value = []): CLASS
{
$this->_usedProperties[\'PROPERTY\'] = true;

return $this->PROPERTY[] = new CLASS($value);
}';
$class->addMethod($methodName, $body, [
'COMMENT' => $comment,
'PROPERTY' => $property->getName(),
'CLASS' => $childClass->getFqcn(),
'PARAM_TYPE' => \in_array('mixed', $nodeParameterTypes, true) ? 'mixed' : implode('|', $nodeParameterTypes),
]);
$class->addMethod($methodName, $body, ['COMMENT' => $comment, 'PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
} else {
$body = $hasNormalizationClosures ? '
COMMENTpublic function NAME(string $VAR, PARAM_TYPE $VALUE = []): CLASS|static
$body = '
COMMENTpublic function NAME(string $VAR, mixed $VALUE = []): CLASS|static
{
if (!\is_array($VALUE)) {
$this->_usedProperties[\'PROPERTY\'] = true;
Expand All @@ -333,17 +292,6 @@ public function NAME(string $VAR, TYPE $VALUE): static
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
}

return $this->PROPERTY[$VAR];
}' : '
COMMENTpublic function NAME(string $VAR, array $VALUE = []): CLASS
{
if (!isset($this->PROPERTY[$VAR])) {
$this->_usedProperties[\'PROPERTY\'] = true;
$this->PROPERTY[$VAR] = new CLASS($VALUE);
} elseif (1 < \func_num_args()) {
throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
}

return $this->PROPERTY[$VAR];
}';
$class->addUse(InvalidConfigurationException::class);
Expand All @@ -352,7 +300,6 @@ public function NAME(string $VAR, TYPE $VALUE): static
'CLASS' => $childClass->getFqcn(),
'VAR' => '' === $key ? 'key' : $key,
'VALUE' => 'value' === $key ? 'data' : 'value',
'PARAM_TYPE' => \in_array('mixed', $prototypeParameterTypes, true) ? 'mixed' : implode('|', $prototypeParameterTypes),
]);
}

Expand Down Expand Up @@ -380,33 +327,35 @@ public function NAME($value): static
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
}

private function getParameterTypes(NodeInterface $node): array
private function getParameterType(NodeInterface $node): ?string
{
$paramTypes = [];
if ($node instanceof BaseNode) {
$types = $node->getNormalizedTypes();
if (\in_array(ExprBuilder::TYPE_ANY, $types, true)) {
$paramTypes[] = 'mixed';
}
if (\in_array(ExprBuilder::TYPE_STRING, $types, true)) {
$paramTypes[] = 'string';
}
}
if ($node instanceof BooleanNode) {
$paramTypes[] = 'bool';
} elseif ($node instanceof IntegerNode) {
$paramTypes[] = 'int';
} elseif ($node instanceof FloatNode) {
$paramTypes[] = 'float';
} elseif ($node instanceof EnumNode) {
$paramTypes[] = 'mixed';
} elseif ($node instanceof ArrayNode) {
$paramTypes[] = 'array';
} elseif ($node instanceof VariableNode) {
$paramTypes[] = 'mixed';
return 'bool';
}

if ($node instanceof IntegerNode) {
return 'int';
}

return array_unique($paramTypes);
if ($node instanceof FloatNode) {
return 'float';
}

if ($node instanceof EnumNode) {
return '';
}

if ($node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode) {
// This is just an array of variables
return 'array';
}

if ($node instanceof VariableNode) {
// mixed
return '';
}

return null;
}

private function getComment(BaseNode $node): string
Expand All @@ -428,8 +377,11 @@ private function getComment(BaseNode $node): string
if ($node instanceof EnumNode) {
$comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => !$a instanceof \UnitEnum ? var_export($a, true) : '\\'.ltrim(var_export($a, true), '\\'), $node->getValues()))))."\n";
} else {
$parameterTypes = $this->getParameterTypes($node);
$comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n";
$parameterType = $this->getParameterType($node);
if (null === $parameterType || '' === $parameterType) {
$parameterType = 'mixed';
}
$comment .= ' * @param ParamConfigurator|'.$parameterType.' $value'."\n";
}
} else {
foreach ((array) ($node->getExample() ?? []) as $example) {
Expand Down Expand Up @@ -592,9 +544,4 @@ private function hasNormalizationClosures(NodeInterface $node): bool

return [] !== $r->getValue($node);
}

private function getType(string $classType, bool $hasNormalizationClosures): string
{
return $classType.($hasNormalizationClosures ? '|scalar' : '');
}
}
23 changes: 0 additions & 23 deletions src/Symfony/Component/Config/Definition/BaseNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ abstract class BaseNode implements NodeInterface
protected $name;
protected $parent;
protected $normalizationClosures = [];
protected $normalizedTypes = [];
protected $finalValidationClosures = [];
protected $allowOverwrite = true;
protected $required = false;
Expand Down Expand Up @@ -236,28 +235,6 @@ public function setNormalizationClosures(array $closures)
$this->normalizationClosures = $closures;
}

/**
* Sets the list of types supported by normalization.
*
* see ExprBuilder::TYPE_* constants.
*
* @return void
*/
public function setNormalizedTypes(array $types)
{
$this->normalizedTypes = $types;
}

/**
* Gets the list of types supported by normalization.
*
* see ExprBuilder::TYPE_* constants.
*/
public function getNormalizedTypes(): array
{
return $this->normalizedTypes;
}

/**
* Sets the closures used for final validation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ protected function createNode(): NodeInterface

if ($this->default) {
if (!\is_array($this->defaultValue)) {
throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.', $node->getPath()));
throw new \InvalidArgumentException(sprintf('"%s": the default value of an array node has to be an array.', $node->getPath()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be reverted, that's a false-positive from fabbot I suppose

}

$node->setDefaultValue($this->defaultValue);
Expand Down Expand Up @@ -406,7 +406,6 @@ protected function createNode(): NodeInterface

if (isset($this->normalization)) {
$node->setNormalizationClosures($this->normalization->before);
$node->setNormalizedTypes($this->normalization->declaredTypes);
$node->setXmlRemappings($this->normalization->remappings);
}

Expand Down
Loading