[RFC] [Discussion] Add values() Method to BackedEnum

php.internals

Mikhail Savin

295 days ago
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

Удальцов Валентин

294 days ago
пн, 10 нояб. 2025 г. в 08:44, Mikhail Savin <mikhail.d.savin@gmail.com>:
> 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

Mikhail Savin

294 days ago
пн, 10 нояб. 2025 г. в 23:55, Valentin Udaltsov <udaltsov.valentin@gmail.com
>:
> пн, 10 нояб. 2025 г. в 08:44, Mikhail Savin <mikhail.d.savin@gmail.com>: > >> 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

Alexandru Pătrănescu

294 days ago
On Tue, Nov 11, 2025 at 10:29 AM Mikhail Savin <mikhail.d.savin@gmail.com> wrote:
> > 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 >
Do you think you can quantify this BC break: how many implementations are having a different signature? And update the RFC "Impact on Ecosystem" section? Small note: implementations usually use `array_column(self::cases(), 'value')` instead of `array_map(static fn(self $c) => $c->value, self::cases())`. and that's just normal, as it is 2x faster: https://3v4l.org/Q5AYg#v8.4.14 maybe mention it as well, or instead of the one you had it the examples, as this is usually how it can be found in libraries.
-- Alex

Derick Rethans

294 days ago
On Tue, 11 Nov 2025, Mikhail Savin wrote:
> 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
Right now, enums can have a "values()" function that can return anything I desire: an array of enum cases, or objects created from these, or strings, or M_PI. If it has to be compatible with `BackedEnum::values(): array(int|string)` (which is what your contract demands, even though it can't express the int|string part) Then that is a BC break again. cheers, Derick

Mikhail Savin

293 days ago
вт, 11 нояб. 2025 г. в 14:50, Derick Rethans <derick@php.net>:
> On Tue, 11 Nov 2025, Mikhail Savin wrote: > > > 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 > > Right now, enums can have a "values()" function that can return anything > I desire: an array of enum cases, or objects created from these, or > strings, or M_PI. > > If it has to be compatible with > > `BackedEnum::values(): array(int|string)` > > (which is what your contract demands, even though it can't express the > int|string part) > > Then that is a BC break again. > > cheers, > Derick
Hi, guys, thanks for feedback Following the discussion with Alex and Derick, I've completed a comprehensive BC analysis. I need community input on a key decision: whether to include a return type in the interface declaration. ## The Two Options **Option A: WITH return type (current PR)** ```php interface BackedEnum { public static function values(): array; } ``` - BC Breaks: 71-600 implementations (1.0-8.8%) - Better type safety and IDE support - Consistent with cases(): array **Option B: WITHOUT return type (alternative)** ```php interface BackedEnum { public static function values(); } ``` - BC Breaks: 0 (0%) - All existing implementations compatible - Can add `: array` in PHP 9.0 ## BC Analysis Data Total enums with values(): 6,800 - Compatible (: array): 6,200 (91.2%) - Missing return type: 64 (0.9%) - Wrong return types: 7 (0.1%) - Unaccounted: ~529 (7.8%) All GitHub search links: https://github.com/php/php-src/pull/20398 ## Question for Community Which approach should we take for PHP 8.6? **Option A:** Accept 1-9% BC break for full type safety **Option B:** Zero BC breaks, add typing in PHP 9.0 I'm inclined toward Option B (zero breaks for 8.6), but want to hear community preference before changing the implementation. Thoughts? --- Additional context: - Implementation change is trivial (one line) - Native implementation returns array regardless of interface - Alex's array_column suggestion incorporated (+3.6k usages) - All data verifiable via GitHub searches in PR Best regards, Mikhail

Joseph Leedy

293 days ago
> ## Question for Community > > Which approach should we take for PHP 8.6? > > **Option A:** Accept 1-9% BC break for full type safety > **Option B:** Zero BC breaks, add typing in PHP 9.0 > > I'm inclined toward Option B (zero breaks for 8.6), but want to hear > community > preference before changing the implementation. > > Thoughts?
I think option B is the best way forward, in my opinion.

Faizan Akram Dar

293 days ago
On Wed, 12 Nov 2025, 19:30 Mikhail Savin, <mikhail.d.savin@gmail.com> wrote:
> > Hi, guys, thanks for feedback > > Following the discussion with Alex and Derick, I've completed a > comprehensive BC > analysis. I need community input on a key decision: whether to include a > return > type in the interface declaration. > > ## The Two Options > > **Option A: WITH return type (current PR)** > ```php > interface BackedEnum { > public static function values(): array; > } > ``` > - BC Breaks: 71-600 implementations (1.0-8.8%) > - Better type safety and IDE support > - Consistent with cases(): array > > **Option B: WITHOUT return type (alternative)** > ```php > interface BackedEnum { > public static function values(); > } > ``` > - BC Breaks: 0 (0%) > - All existing implementations compatible > - Can add `: array` in PHP 9.0 > > ## BC Analysis Data > > Total enums with values(): 6,800 > - Compatible (: array): 6,200 (91.2%) > - Missing return type: 64 (0.9%) > - Wrong return types: 7 (0.1%) > - Unaccounted: ~529 (7.8%) > > All GitHub search links: https://github.com/php/php-src/pull/20398 > > ## Question for Community > > Which approach should we take for PHP 8.6? > > **Option A:** Accept 1-9% BC break for full type safety > **Option B:** Zero BC breaks, add typing in PHP 9.0 > > I'm inclined toward Option B (zero breaks for 8.6), but want to hear > community > preference before changing the implementation. > > Thoughts? > > --- > Additional context: > - Implementation change is trivial (one line) > - Native implementation returns array regardless of interface > - Alex's array_column suggestion incorporated (+3.6k usages) > - All data verifiable via GitHub searches in PR > > > Best regards, Mikhail > >
Hi Mikhail, I personally don't see any added value by adding such a method to enums at this point when getting the equivalent results is already possible by a one liner. `array_column(EnumFqcn::cases(), 'value');` Kind regards, Faizan

Tim Düsterhus

282 days ago
Hi On 11/12/25 19:42, Faizan Akram Dar wrote:
> I personally don't see any added value by adding such a method to enums at > this point when getting the equivalent results is already possible by a one > liner. > > `array_column(EnumFqcn::cases(), 'value');`
I agree with that. I feel that BackedEnums are already over-used to the point where they just act as “fancy strings” instead of a proper data type on their own and I don't think we should further encourage treating enums as fancy strings. In addition to that, a custom implementation of the method is trivially done as shown by Faizan and doesn't even need to be made available by the author of the enum but can be written on demand when having all values (without the corresponding names) is necessary. And even when there is not a BC break right now, by not actually fixating the signature in the interface, it would be with PHP 9.0 and from then on it would (needlessly) block another method on enums that folks might otherwise be interested in using, since the name is very generic. Even when never adding it to the interface - static methods on interfaces are odd - it would break user expectations that some enum methods can be redefined and others can't. Best regards Tim Düsterhus

Larry Garfield

293 days ago
On Wed, Nov 12, 2025, at 5:35 AM, Mikhail Savin wrote:
> Hi, guys, thanks for feedback > > Following the discussion with Alex and Derick, I've completed a > comprehensive BC > analysis. I need community input on a key decision: whether to include > a return > type in the interface declaration. > > ## The Two Options > > **Option A: WITH return type (current PR)** > ```php > interface BackedEnum { > public static function values(): array; > } > ``` > - BC Breaks: 71-600 implementations (1.0-8.8%) > - Better type safety and IDE support > - Consistent with cases(): array > > **Option B: WITHOUT return type (alternative)** > ```php > interface BackedEnum { > public static function values(); > } > ``` > - BC Breaks: 0 (0%) > - All existing implementations compatible > - Can add `: array` in PHP 9.0 > > ## BC Analysis Data > > Total enums with values(): 6,800 > - Compatible (: array): 6,200 (91.2%) > - Missing return type: 64 (0.9%) > - Wrong return types: 7 (0.1%) > - Unaccounted: ~529 (7.8%) > > All GitHub search links: https://github.com/php/php-src/pull/20398 > > ## Question for Community > > Which approach should we take for PHP 8.6? > > **Option A:** Accept 1-9% BC break for full type safety > **Option B:** Zero BC breaks, add typing in PHP 9.0 > > I'm inclined toward Option B (zero breaks for 8.6), but want to hear community > preference before changing the implementation. > > Thoughts? > > --- > Additional context: > - Implementation change is trivial (one line) > - Native implementation returns array regardless of interface > - Alex's array_column suggestion incorporated (+3.6k usages) > - All data verifiable via GitHub searches in PR
Isn't this what the #[ReturnTypeWillChange] attribute was intended for? cf: https://wiki.php.net/rfc/internal_method_return_types That seems like an option C? It's technically a BC break, but people can just drop an attribute on it to disable the return type check until 9.0. We did the same for the various internal methods, as noted in that RFC. --Larry Garfield

Niels Dossche

293 days ago
On 12/11/2025 20:01, Larry Garfield wrote:
> Isn't this what the #[ReturnTypeWillChange] attribute was intended for? > > cf: https://wiki.php.net/rfc/internal_method_return_types > > That seems like an option C? It's technically a BC break, but people can just drop an attribute on it to disable the return type check until 9.0. We did the same for the various internal methods, as noted in that RFC.
Parameter checks are not disabled by that attribute, so the BC problem remains.

Mikhail Savin

289 days ago
ср, 12 нояб. 2025 г. в 22:26, Niels Dossche <dossche.niels@gmail.com>:
> > > On 12/11/2025 20:01, Larry Garfield wrote: > > Isn't this what the #[ReturnTypeWillChange] attribute was intended for? > > > > cf: https://wiki.php.net/rfc/internal_method_return_types > > > > That seems like an option C? It's technically a BC break, but people > can just drop an attribute on it to disable the return type check until > 9.0. We did the same for the various internal methods, as noted in that > RFC. > > Parameter checks are not disabled by that attribute, so the BC problem > remains. >
Hi everyone, Based on your feedback, I’ve updated both the PR and the RFC by removing the return type declaration from the interface. Thanks for the input.