Skip to content
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

[Config] Allow enum values in EnumNode #49098

Merged
merged 1 commit into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ private function getComment(BaseNode $node): string
}

if ($node instanceof EnumNode) {
$comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => var_export($a, true), $node->getValues()))))."\n";
$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";
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/Config/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.3
---

* Allow enum values in `EnumNode`

6.2
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal
FloatNode::class,
IntegerNode::class => 'numeric value',
BooleanNode::class => 'true|false',
EnumNode::class => implode('|', array_unique(array_map('json_encode', $prototype->getValues()))),
EnumNode::class => $prototype->getPermissibleValues('|'),
default => 'value',
};
}
Expand Down Expand Up @@ -149,7 +149,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal
}

if ($child instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_unique(array_map('json_encode', $child->getValues())));
$comments[] = 'One of '.$child->getPermissibleValues('; ');
}

if (\count($comments)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private function writeNode(NodeInterface $node, NodeInterface $parentNode = null
}
}
} elseif ($node instanceof EnumNode) {
$comments[] = 'One of '.implode('; ', array_unique(array_map('json_encode', $node->getValues())));
$comments[] = 'One of '.$node->getPermissibleValues('; ');
$default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
} elseif (VariableNode::class === $node::class && \is_array($example)) {
// If there is an array example, we are sure we dont need to print a default value
Expand Down
37 changes: 34 additions & 3 deletions src/Symfony/Component/Config/Definition/EnumNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,16 @@ public function __construct(?string $name, NodeInterface $parent = null, array $
}

foreach ($values as $value) {
if (null !== $value && !\is_scalar($value)) {
throw new \InvalidArgumentException(sprintf('"%s" only supports scalar or null values, "%s" given.', __CLASS__, get_debug_type($value)));
if (null === $value || \is_scalar($value)) {
continue;
}

if (!$value instanceof \UnitEnum) {
throw new \InvalidArgumentException(sprintf('"%s" only supports scalar, enum, or null values, "%s" given.', __CLASS__, get_debug_type($value)));
}

if ($value::class !== ($enumClass ??= $value::class)) {
throw new \InvalidArgumentException(sprintf('"%s" only supports one type of enum, "%s" and "%s" passed.', __CLASS__, $enumClass, $value::class));
}
}

Expand All @@ -43,12 +51,35 @@ public function getValues()
return $this->values;
}

/**
* @internal
*/
public function getPermissibleValues(string $separator): string
{
return implode($separator, array_unique(array_map(static function (mixed $value): string {
if (!$value instanceof \UnitEnum) {
return json_encode($value);
}

return ltrim(var_export($value, true), '\\');
}, $this->values)));
}

protected function validateType(mixed $value)
{
if ($value instanceof \UnitEnum) {
return;
}

parent::validateType($value);
}

protected function finalizeValue(mixed $value): mixed
{
$value = parent::finalizeValue($value);

if (!\in_array($value, $this->values, true)) {
$ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_unique(array_map('json_encode', $this->values)))));
$ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', ')));
$ex->setPath($this->getPath());

throw $ex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Tests\Fixtures\TestEnum;

class PrimitiveTypes implements ConfigurationInterface
{
Expand All @@ -23,7 +24,7 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode
->children()
->booleanNode('boolean_node')->end()
->enumNode('enum_node')->values(['foo', 'bar', 'baz'])->end()
->enumNode('enum_node')->values(['foo', 'bar', 'baz', TestEnum::Bar])->end()
->floatNode('float_node')->end()
->integerNode('integer_node')->end()
->scalarNode('scalar_node')->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function booleanNode($value): static

/**
* @default null
* @param ParamConfigurator|'foo'|'bar'|'baz' $value
* @param ParamConfigurator|'foo'|'bar'|'baz'|\Symfony\Component\Config\Tests\Fixtures\TestEnum::Bar $value
* @return $this
*/
public function enumNode($value): static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private function getConfigurationAsString()
<!-- scalar-deprecated: Deprecated (Since vendor/package 1.1: The child node "scalar_deprecated" at path "acme_root" is deprecated.) -->
<!-- scalar-deprecated-with-message: Deprecated (Since vendor/package 1.1: Deprecation custom message for "scalar_deprecated_with_message" at "acme_root") -->
<!-- enum-with-default: One of "this"; "that" -->
<!-- enum: One of "this"; "that" -->
<!-- enum: One of "this"; "that"; Symfony\Component\Config\Tests\Fixtures\TestEnum::Ccc -->
<!-- variable: Example: foo, bar -->
<config
boolean="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private function getConfigurationAsString(): string
scalar_deprecated_with_message: ~ # Deprecated (Since vendor/package 1.1: Deprecation custom message for "scalar_deprecated_with_message" at "acme_root")
node_with_a_looong_name: ~
enum_with_default: this # One of "this"; "that"
enum: ~ # One of "this"; "that"
enum: ~ # One of "this"; "that"; Symfony\Component\Config\Tests\Fixtures\TestEnum::Ccc
# some info
array:
Expand Down
21 changes: 16 additions & 5 deletions src/Symfony/Component/Config/Tests/Definition/EnumNodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Tests\Fixtures\TestEnum;
use Symfony\Component\Config\Tests\Fixtures\TestEnum2;

class EnumNodeTest extends TestCase
{
public function testFinalizeValue()
{
$node = new EnumNode('foo', null, ['foo', 'bar']);
$node = new EnumNode('foo', null, ['foo', 'bar', TestEnum::Bar]);
$this->assertSame('foo', $node->finalize('foo'));
$this->assertSame(TestEnum::Bar, $node->finalize(TestEnum::Bar));
}

public function testConstructionWithNoValues()
Expand Down Expand Up @@ -51,8 +54,8 @@ public function testConstructionWithNullName()
public function testFinalizeWithInvalidValue()
{
$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar"');
$node = new EnumNode('foo', null, ['foo', 'bar']);
$this->expectExceptionMessage('The value "foobar" is not allowed for path "foo". Permissible values: "foo", "bar", Symfony\Component\Config\Tests\Fixtures\TestEnum::Foo');
$node = new EnumNode('foo', null, ['foo', 'bar', TestEnum::Foo]);
$node->finalize('foobar');
}

Expand Down Expand Up @@ -80,11 +83,19 @@ public function testSameStringCoercedValuesAreDifferent()
$this->assertNull($node->finalize(null));
}

public function testNonScalarOrNullValueThrows()
public function testNonScalarOrEnumOrNullValueThrows()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('"Symfony\Component\Config\Definition\EnumNode" only supports scalar or null values, "stdClass" given.');
$this->expectExceptionMessage('"Symfony\Component\Config\Definition\EnumNode" only supports scalar, enum, or null values, "stdClass" given.');

new EnumNode('ccc', null, [new \stdClass()]);
}

public function testTwoDifferentEnumsThrows()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('"Symfony\Component\Config\Definition\EnumNode" only supports one type of enum, "Symfony\Component\Config\Tests\Fixtures\TestEnum" and "Symfony\Component\Config\Tests\Fixtures\TestEnum2" passed.');

new EnumNode('ccc', null, [...TestEnum::cases(), TestEnum2::Ccc]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Tests\Fixtures\TestEnum;

class ExampleConfiguration implements ConfigurationInterface
{
Expand All @@ -38,7 +39,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('scalar_deprecated_with_message')->setDeprecated('vendor/package', '1.1', 'Deprecation custom message for "%node%" at "%path%"')->end()
->scalarNode('node_with_a_looong_name')->end()
->enumNode('enum_with_default')->values(['this', 'that'])->defaultValue('this')->end()
->enumNode('enum')->values(['this', 'that'])->end()
->enumNode('enum')->values(['this', 'that', TestEnum::Ccc])->end()
->arrayNode('array')
->info('some info')
->canBeUnset()
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/Config/Tests/Fixtures/TestEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Symfony\Component\Config\Tests\Fixtures;

enum TestEnum
{
case Foo;
case Bar;
case Ccc;
}
10 changes: 10 additions & 0 deletions src/Symfony/Component/Config/Tests/Fixtures/TestEnum2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Symfony\Component\Config\Tests\Fixtures;

enum TestEnum2
{
case Foo;
case Bar;
case Ccc;
}