-
-
Notifications
You must be signed in to change notification settings - Fork 980
Description
API Platform version(s) affected
3.4.x (likely affects all versions using Symfony BackedEnumNormalizer)
Description
When using PHP Backed Enums as property values inside an ApiResource, invalid enum values sent by a client never reach Symfony Validator.
Instead, the request fails during deserialization, and API Platform returns a generic 400 error:
The data must belong to a backed enumeration of type App\BusinessLogic\Constants\GoalType
This happens before validation, which means:
- Validation constraints (
Assert\Choice,Assert\NotNull, etc.) are never executed - Validation groups (
validationContext) are ignored - The error cannot be handled consistently through API Platform’s
ConstraintViolationList(422)
This is problematic for public APIs because enum validation becomes inconsistent and impossible to centralize through validation groups.
How to reproduce
Entity:
#[ORM\Entity]
class FitnessProfile
{
#[ORM\Column(nullable: true, enumType: GoalType::class)]
#[Assert\Choice(
callback: [GoalType::class, 'values'],
groups: ['api:create', 'api:update'],
)]
private ?GoalType $goal = null;
}namespace App\BusinessLogic\Constants;
enum GoalType: string
{
case LOSE_WEIGHT = 'fat_loss';
case BUILD_MUSCLE = 'muscle_gain';
case GET_STRONGER = 'get_stronger';
case ENDURANCE = 'endurance';
case MAINTENANCE = 'maintenance';
case PERFORMANCE = 'performance';
}Resource:
#[ApiResource(
operations: [
new Post(
uriTemplate: '/lead',
denormalizationContext: ['groups' => ['api:create']],
validationContext: ['groups' => 'api:create'],
)
]
)]
class Lead
{
#[Assert\Valid(groups: ['api:create', 'api:update'])]
private ?FitnessProfile $fitnessProfile = null;
}Request:
{
"fitnessProfile": {
"goal": "muscle2_gain"
}
}Actual Result
API Platform fails during deserialization:
400 Bad Request
{
"title": "An error occurred",
"detail": "The data must belong to a backed enumeration of type App\\BusinessLogic\\Constants\\GoalType"
}
Stack trace shows failure in:
Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer::denormalize()
Validator is never executed.
Expected Result
Validation should run and produce a 422 with a constraint violation, respecting validation groups:
{
"violations": [
{
"propertyPath": "fitnessProfile.goal",
"message": "This value is not valid."
}
]
}In other words:
Enum deserialization errors should behave like normal validation errors, not bypass validation entirely.
Possible Solutions
1️⃣ Delay enum coercion until after validation
Let validation handle “invalid enum value” the same way Choice currently does.
2️⃣ Convert enum denormalization errors into ConstraintViolationList
Instead of throwing immediately, convert NotNormalizableValueException into a violation.
3️⃣ Provide documented official pattern
For example, recommended use of:
- Input DTOs with string + validation
- or Serializer option (
ALLOW_INVALID_VALUES) enabled by default in API Platform contexts
…but today none of these approaches are officially supported or explained.
Additional Context
- Behavior originates from Symfony
BackedEnumNormalizer - Related issue discussed previously but closed as stale: enums fail before validation (Support for Enums core#2254; Enums not working after upgrade to 2.7 / 3.0 core#5040; [3.4] Improving handling of BackedEnums with ApiResource class attributes core#6298; Validation doesn't work proppperly with strict types core#6392; [GraphQL] PHP 8.1 backed enums not properly deserialized from JavaScript/TypeScript client core#7009; Wrong error code when denormalizing a non existant item core#6116; How to validate entity's proprty with enum type core#5984)
- This makes enums hard to use in APIs with validation groups, especially when building public APIs where error consistency matters
Thanks for your work — enums as resources are great, but validation consistency would make them far more usable in real-world APIs.