пн, 10 нояб. 2025 г. в 23:55, Valentin Udaltsov <[email protected]
>:

> пн, 10 нояб. 2025 г. в 08:44, Mikhail Savin <[email protected]>:
>
>> Hi internals,
>>
>> I've created an RFC to add a native values() method to BackedEnum:
>>
>> https://wiki.php.net/rfc/add_values_method_to_backed_enum
>>
>> == Summary ==
>>
>> The RFC proposes adding BackedEnum::values() that returns an indexed
>> array
>> of all backing values. The implementation uses conditional registration -
>> the native method is only added when the enum doesn't already define
>> values(), ensuring ZERO backward compatibility breaks.
>>
>> Key points:
>>   * Native values() added automatically to new enums
>>   * Existing enums with custom values() continue working unchanged
>>   * Trait-based implementations are respected
>>   * Libraries can maintain their implementation for older PHP versions
>>   * Solves boilerplate problem (3,860+ implementations, ~24k-44k real
>> usage)
>>
>> Common use cases:
>>   * Database migrations: $table->enum('status', Status::values())
>>   * Form validation: in_array($input, Status::values())
>>   * API responses: ['allowed_values' => Status::values()]
>>
>> == Implementation ==
>>
>> Working implementation with conditional registration:
>> https://github.com/php/php-src/pull/20398
>>
>> The engine checks if values() exists before registering the native
>> version:
>>   - User-defined values() present? Use it.
>>   - No user-defined values()? Add native implementation.
>>
>> == No BC Breaks ==
>>
>> This approach ensures:
>>   * Existing code works unchanged (no migration needed)
>>   * Immediate benefit for new code (automatic values())
>>   * Gradual adoption possible (libraries can migrate at their pace)
>>
>> Trade-off: Makes values() the only overridable enum method (unlike
>> cases/from/tryFrom). The RFC documents this as a pragmatic choice -
>> solving real problems without forced migrations.
>>
>> == Questions ==
>>
>> 1. Is the conditional approach technically sound?
>> 2. Is the API consistency trade-off acceptable given the zero BC breaks?
>> 3. Any concerns with the implementation approach?
>> 4. Should I implement some steps from "Future scope" of the rfc now?
>>
>> Discussion period: 2 weeks minimum before voting.
>>
>> Looking forward to your feedback!
>>
>> Best regards,
>> Savin Mikhail
>>
>
> Hi, Mikhail!
>
> Thank you for the RFC.
>
> Consider this code if this RFC is accepted:
>
> enum EnumWithUserDefinedValuesMethod: string
> {
>     case X = 'x';
>
>     public static function values(): string
>     {
>         return 'values';
>     }
> }
>
> function getBackedEnumValues(BackedEnum $enum): array
> {
>     return $enum::values();
> }
>
> getBackedEnumValues(EnumWithUserDefinedValuesMethod::X);
>
>
> This code will suddenly break, because inside getBackedEnumValues I can
> safely assume that BackedEnum::values() returns an array, since it's a part
> of the interface contract.
>
> However, EnumWithUserDefinedValuesMethod breaks the Liskov Substitution
> Principle by defining a method with a non-compatible return type and gives
> a runtime error.
>
> What you've basically suggested is to ignore the LSP. This is not a good
> idea.
>
> --
> Best regards, Valentin
>


Thanks for raising the LSP concern. You’re right that today a userland enum
can define a values() with an arbitrary signature, and a helper like:

function getBackedEnumValues(BackedEnum $enum): array {
    return $enum::values();
}

could blow up at runtime if that user method returns a non-array.

The RFC addresses this by adding "public static function values(): array"
to the BackedEnum interface itself.
With that in place, an enum that defines an incompatible values() will fail
at compile time with a normal method-signature incompatibility, exactly
like it would for from()/tryFrom().

Here’s a .phpt demonstrating the intended behavior:

--TEST--
Backed enums: user-defined values() incompatible with interface signature
--FILE--
<?php

enum E: string {
    case A = 'a';

    // Intentional incompatibility: interface requires array return type
    public static function values(): string {
        return 'values';
    }
}

?>
--EXPECTF--
Fatal error: Declaration of E::values(): string must be compatible with
BackedEnum::values(): array in %s on line %d

Run with:
TEST_PHP_EXECUTABLE=sapi/cli/php sapi/cli/php -n run-tests.php -q
Zend/tests/enum/backed-values-user-defined-incompatible.phpt

So LSP isn’t being ignored; it’s enforced by the interface method.

That said, adding values() to BackedEnum is a source-level BC break for
codebases that already define conflicting values() on backed enums.

But, as we can see from GitHub searches that I posted in PR, most of the
implementations is exactly the same, so BC break is very small

Reply via email to