Skip to content

Object values in an enum are silently deduplicated, dropping legal values #190

Open

Description

@wol-soft

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]]);  // REJECTED
Value 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]]));  // 1

Cause

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_REGULAR uses loose comparison for scalars, so it could merge entries a strict reading keeps
    distinct (0 / "0" / false, or 1 / 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 default value 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 default that is one of the allowed object values (exercises the strict in_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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions