Repository navigation
Object values in an enum are silently deduplicated, dropping legal values #190
Description
Summary
EnumValidatorFactory::modify() deduplicates the allowed values with array_unique($allowedValues).
array_unique() defaults to SORT_STRING, which casts every element to a string before comparing —
so every array-valued (object or array) enum entry stringifies to the literal "Array" and they
all compare equal to each other.
The result is silent data loss: a schema listing several object values in an enum generates a class
that only accepts the first of them and rejects the rest, even though its own schema explicitly
permits them.
Pre-existing on master. Found while probing #181; unrelated to that work, so it is filed
separately.
Reproduction
Legal JSON Schema — two distinct object values in one enum:
{
"type": "object",
"properties": {
"p": {
"enum": [
{
"a": 1
},
{
"b": 2
}
]
}
}
}Generation emits a PHP warning:
Warning: Array to string conversion in src/Model/Validator/Factory/Any/EnumValidatorFactory.php
and the generated class then behaves like this:
new Root(['p' => ['a' => 1]]); // accepted
new Root(['p' => ['b' => 2]]); // REJECTEDValue for 'p' must be one of [{"a":1}], got {"b":2}
The truncated allowed-value list is visible in the message itself — {"b": 2} was dropped from the
validator entirely, so the exception reports an allowed set that does not match the schema.
The underlying behaviour, in isolation:
count(array_unique([['a' => 1], ['b' => 2], ['c' => 3]])); // 1Cause
EnumValidatorFactory::modify() calls array_unique($allowedValues) without a sort flag. The
default SORT_STRING stringifies each value; for any array value that is the literal "Array",
which additionally raises the Array to string conversion warning.
Scalar enums are unaffected, which is why this has gone unnoticed — every existing enum fixture in
the test suite uses scalar values.
Suggested fix
array_unique($allowedValues, SORT_REGULAR) compares arrays structurally rather than stringifying
them, and removes the warning.
Two things to verify before settling on that flag:
SORT_REGULARuses loose comparison for scalars, so it could merge entries a strict reading keeps
distinct (0/"0"/false, or1/true). If that turns out to be a real regression for
mixed-type enums, deduplicate on a serialized key (json_encode) instead and keep the original
values, so both the dedup and the stored values stay faithful.- Immediately below the dedup, the
defaultvalue is validated with
in_array($json['default'], $allowedValues, true). Strict comparison there must keep working for
object defaults.
Test coverage to add
No existing fixture uses a non-scalar enum, so all of these are new:
- two distinct object values — both accepted, neither dropped;
- genuinely duplicated object values — deduplicated to one;
- a mixed scalar/object enum;
- an object
defaultthat is one of the allowed object values (exercises the strictin_array
above).
Assert the complete exception message rather than a substring, per CLAUDE.md.
Note
While in this file: the closure filtering null out of the derived enum types uses the
single-character variable name $t, which CLAUDE.md forbids. Worth fixing in the same pass.
The production library should not need a matching change — only the content of the allowed-value
list changes, not the message format.
Activity