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 ==
- Is the conditional approach technically sound?
- Is the API consistency trade-off acceptable given the zero BC breaks?
- Any concerns with the implementation approach?
- 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
пн, 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/20398The 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 ==
- Is the conditional approach technically sound?
- Is the API consistency trade-off acceptable given the zero BC breaks?
- Any concerns with the implementation approach?
- 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
пн, 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/20398The 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 ==
- Is the conditional approach technically sound?
- Is the API consistency trade-off acceptable given the zero BC breaks?
- Any concerns with the implementation approach?
- 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 MikhailHi, 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
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
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--
<?phpenum 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
вт, 11 нояб. 2025 г. в 14:50, Derick Rethans derick@php.net:
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--
<?phpenum 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 %dRight 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)
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)
interface BackedEnum {
public static function values();
}
- BC Breaks: 0 (0%)
- All existing implementations compatible
- Can add
: arrayin 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
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.0I'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.
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)
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)
interface BackedEnum { public static function values(); }
- BC Breaks: 0 (0%)
- All existing implementations compatible
- Can add
: arrayin PHP 9.0BC 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.0I'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
Hi
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
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)
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)
interface BackedEnum { public static function values(); }
- BC Breaks: 0 (0%)
- All existing implementations compatible
- Can add
: arrayin PHP 9.0BC 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.0I'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
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.
ср, 12 нояб. 2025 г. в 22:26, Niels Dossche dossche.niels@gmail.com:
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.