Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Validator] Introduce BackedEnumValue constraint #54226

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

Open
wants to merge 5 commits into
base: 7.3
Choose a base branch
Loading
from

Conversation

AurelienPillevesse
Copy link
Contributor

@AurelienPillevesse AurelienPillevesse commented Mar 10, 2024

Q A
Branch? 7.3
Bug fix? no
New feature? yes
Deprecations? no
Issues -
License MIT

My use case: I receive data in my DTOs that we only want if this value exists in an Enum and reject the whole thing at validation otherwise.

@carsonbot carsonbot added Status: Needs Review Feature RFC RFC = Request For Comments (proposals about features that you want to be discussed) Validator labels Mar 10, 2024
@carsonbot carsonbot added this to the 7.1 milestone Mar 10, 2024
@carsonbot carsonbot changed the title [RFC][Validator] Introduce Enum constraint [Validator] Introduce Enum constraint Mar 10, 2024
@AurelienPillevesse AurelienPillevesse changed the title [Validator] Introduce Enum constraint [Validator] Introduce Enum constraint Mar 10, 2024
@derrabus
Copy link
Member

Did you read the discussion on #43047?

@AurelienPillevesse
Copy link
Contributor Author

Didn't see it before, my bad

@AurelienPillevesse
Copy link
Contributor Author

It's a dedicated constraint so why not but I understand if your point of view is to not accept it and adding the code below (as mentioned in the other discussion) if people want to accept an Enum value with #[MapRequestPayload] for example :

public static function values(): array
{
    return array_column(self::cases(), 'value');
}

Because you are speaking about this topic, can you maybe give me feedback about this PR : symfony/symfony-docs#19590 (if it's added in the documentation, we will probably less try to find a way to achieve it)

@derrabus
Copy link
Member

I understand if your point of view is to not accept it

I didn't say that. It's just that there has been a PR on that topic a while ago, and if we revisit the topic, we should take that prior discussion into account.

src/Symfony/Component/Validator/ConstraintValidator.php Outdated Show resolved Hide resolved
src/Symfony/Component/Validator/Constraints/Enum.php Outdated Show resolved Hide resolved
src/Symfony/Component/Validator/Constraints/Enum.php Outdated Show resolved Hide resolved
src/Symfony/Component/Validator/Constraints/Enum.php Outdated Show resolved Hide resolved
src/Symfony/Component/Validator/Constraints/Enum.php Outdated Show resolved Hide resolved
@AurelienPillevesse AurelienPillevesse requested a review from stof March 11, 2024 21:14
@AurelienPillevesse AurelienPillevesse changed the title [Validator] Introduce Enum constraint [Validator] Introduce BackedEnumValue constraint Mar 11, 2024
@fabpot fabpot modified the milestones: 7.2, 7.3 Nov 20, 2024
@AurelienPillevesse AurelienPillevesse force-pushed the introduce-enum-constraint branch 4 times, most recently from e48670d to edf0705 Compare December 10, 2024 19:04
@AurelienPillevesse
Copy link
Contributor Author

AurelienPillevesse commented Dec 12, 2024

@fabpot @nicolas-grekas
When I add my line in the changelog, it's telling me that I've a conflict.
If I'm fixing the conflict with github UI, the conflict is solved but it merge the branch 7.3 and fabbot is not happy

Can you tell me how to handle it properly?

@stof
Copy link
Member

stof commented Dec 12, 2024

@AurelienPillevesse you should rebase your branch on top of the latest upstream 7.3 branch instead of merging. See https://symfony.com/doc/current/contributing/code/pull_requests.html#rebase-your-pull-request for the detailed explanation.

@AurelienPillevesse
Copy link
Contributor Author

Thanks @stof

parent::__construct([], $groups, $payload);

if (!is_a($type, \BackedEnum::class, true)) {
throw new ConstraintDefinitionException(\sprintf('The "type" must be a \BackedEnum, got "%s".', get_debug_type($type)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
throw new ConstraintDefinitionException(\sprintf('The "type" must be a \BackedEnum, got "%s".', get_debug_type($type)));
throw new ConstraintDefinitionException(\sprintf('The "type" must be a "\BackedEnum", got "%s".', get_debug_type($type)));

) {
parent::__construct([], $groups, $payload);

if (!is_a($type, \BackedEnum::class, true)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (!is_a($type, \BackedEnum::class, true)) {
if (!is_subclass_of($type, \BackedEnum::class, true)) {

Copy link
Member

Choose a reason for hiding this comment

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

is_a() looks okay to me?

];

/**
* @param class-string<\BackedEnum> $type the type of the enum
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @param class-string<\BackedEnum> $type the type of the enum
* @param class-string<\BackedEnum> $className the enum class name

Copy link
Member

Choose a reason for hiding this comment

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

first letter should be uppercase also

public string $type,
public array $except = [],
public string $message = 'The value you selected is not a valid choice.',
public string $typeMessage = 'This value should be of type {{ type }}.',
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public string $typeMessage = 'This value should be of type {{ type }}.',
public string $invalidValueTypeMessage = 'The value should be of type {{ type }}.',

public function __construct(
public string $type,
public array $except = [],
public string $message = 'The value you selected is not a valid choice.',
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public string $message = 'The value you selected is not a valid choice.',
public string $invalidValueMessage = 'The value is not a backing value of {{ className }}.',

Comment on lines +48 to +49
->setParameter('{{ value }}', $this->formatValue($value))
->setParameter('{{ choices }}', $this->formatValidCases($constraint))
Copy link
Contributor

Choose a reason for hiding this comment

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

@derrabus is right, you only need to define what is in the constraint message

Copy link
Contributor Author

@AurelienPillevesse AurelienPillevesse Dec 24, 2024

Choose a reason for hiding this comment

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

It's build in a similar way as Choice constraint.

Message :

public string $message = 'The value you selected is not a valid choice.';

Validator :

$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setParameter('{{ choices }}', $this->formatValues($choices))
->setCode(Choice::NO_SUCH_CHOICE_ERROR)
->addViolation();

The value or choice values are not used by default. If you override the default message and use {{ value }} or {{ choice }} on it, it will be replaced in your new message.

}

try {
$enumTypeValue = $constraint->type::tryFrom($value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
$enumTypeValue = $constraint->type::tryFrom($value);
$enumValue = $constraint->type::tryFrom($value);

Comment on lines +67 to +73
return $this->formatValues(array_map(
static fn (\BackedEnum $case) => $case->value,
array_filter(
$constraint->type::cases(),
static fn (\BackedEnum $currentValue) => !\in_array($currentValue, $constraint->except, true),
)
));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return $this->formatValues(array_map(
static fn (\BackedEnum $case) => $case->value,
array_filter(
$constraint->type::cases(),
static fn (\BackedEnum $currentValue) => !\in_array($currentValue, $constraint->except, true),
)
));
return $this->formatValues(array_column(
array_filter(
$constraint->type::cases(),
static fn (\BackedEnum $currentValue) => !\in_array($currentValue, $constraint->except, true),
),
'value',
));

@sfmok
Copy link
Contributor

sfmok commented Dec 19, 2024

I think this change requires setting ALLOW_INVALID_VALUES to true by default in

if ($context[self::ALLOW_INVALID_VALUES] ?? false) {
Otherwise, a typical use of this validator in APIs will throw a NotNormalizableValueException unless ALLOW_INVALID_VALUES is explicitly set to true in the context, which does not make sense IMO.

@stof
Copy link
Member

stof commented Jan 28, 2025

@sfmok this PR is totally unrelated to that normalizer. The constraint added here is meant to be used in case your DTO expects a scalar value that must match the value associated with a case of the backed enum. The BackedEnumNormalizer is triggered in case the DTO expects a BackedEnum instance.

@thePanz
Copy link
Contributor

thePanz commented Mar 6, 2025

Thanks for pointing that out @stof and @sfmok : we should make it clean from the docs that the case is the following:

class MyDto {
    #[Assert\BackedEnumValue(MyStringBackedEnum::class)]
    public string $value = null;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature RFC RFC = Request For Comments (proposals about features that you want to be discussed) Status: Needs Review Validator
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.