Backed enums frequently need an indexed list of their backing values (int|string). Today this is commonly implemented in userland using a helper method or a trait.
The pattern is widespread and implemented many times across the ecosystem.
GitHub code search (2025-11-12) found roughly:
self::cases();function values() (often applied to many enums), which suggests the real usage is significantly higher.More info about this research you can find in PR.
This RFC proposes adding a native values() static method to the BackedEnum interface that returns an indexed array of all backing values in declaration order. By doing that I hope to standardize the most common helper into a native method and reduce boilerplate across projects.
To avoid fatal errors from method name collisions with existing userland implementations, the native implementation is registered only if the enum does not already define a values() method (including via traits). The interface declaration intentionally has no return type.
<?php enum Status: string { case Active = 'active'; case Inactive = 'inactive'; case Archived = 'archived'; } var_dump(Status::values()); // ['active', 'inactive', 'archived'] ?>
Common use cases include:
$table->enum('status', Status::values())Choice(callback: [Status::class, 'values'])['allowed_statuses' => Status::values()]
Add the following method to the BackedEnum interface:
<?php interface BackedEnum extends UnitEnum { /** * Returns an indexed array of all backing values for the enum cases. * * @return int[]|string[] */ public static function values(); } ?>
Semantics when the native implementation is used:
cases() (declaration order).The RFC aims to avoid method name collisions with existing code bases by combining two design choices:
values() method.
As a result, existing enums that already define values() keep their behavior unchanged. New enums (or enums without a user-defined values()) get the native implementation automatically.
Trade-off: values() becomes the only built-in enum-related method that may be user-defined, while cases(), from() and tryFrom() are always native.
Today users can write:
<?php $values = array_column(Status::cases(), 'value'); ?>
However, a native values() method:
cases(), from() and tryFrom();The large number of existing implementations suggests that the ecosystem repeatedly needs this helper.
Next PHP 8.x (PHP 8.6)
IDEs, Language servers, static analyzers, polyfills might need to update their stubs to reflect docblock from updated BackedEnum interface
No impact.
No impact.
The following points were raised during discussion and are explicitly called out for voters to consider:
values() to take precedence) is an acceptable trade-off versus strict consistency with cases()/from()/tryFrom().: array) in a future major version.
If the community later prefers full consistency (non-overridable values() like other enum methods):
Phase 1 (PHP 8.x): Emit E_DEPRECATED for user-defined values()
<?php enum Status: string { case Active = 'active'; public static function values(): array { ... } // Deprecated: Status::values() is provided natively and should not be redeclared } ?>
Phase 2 (PHP 9.0): Make user-defined values() an error
This gives the ecosystem years to migrate while eventually achieving full API consistency.
This future scope is NOT part of the current RFC - just documenting the possibility.
Implementation is provided in PR https://github.com/php/php-src/pull/20398.
High-level changes:
Zend/zend_enum.stub.php and generated Zend/zend_enum_arginfo.hZend/zend_string.h)Zend/zend_enum.c with conditional registrationZend/tests/enum/ and update reflection expectations under ext/reflection/tests/getValues(), toArray()): rejected as less consistent with cases() and less aligned with existing practice.values(): array in the interface: rejected due to potential compatibility issues with existing userland signatures.values(): rejected because it would cause hard failures for existing enums that already declare values().Status::$values): rejected as inconsistent with existing enum APIs and not supported by the language.This is a Yes/No vote, requiring a 2/3 majority.
Voting will start later
The results of GitHub usage examples you can find in PR
Related RFCs:
An alternative implementation was considered where the interface would explicitly declare an : array return type:
<?php interface BackedEnum extends UnitEnum { public static function values(): array; // ← WITH return type } ?>
Advantages:
cases(): array signatureWhy rejected:
Comprehensive GitHub search analysis found 6,800 existing values() implementations:
| Category | Count | % |
|---|---|---|
Compatible: : array return type | 6,200 | 91.2% |
| Missing return type | 64 | 0.9% ❌ |
Incompatible: other types (: string, : Iterator, etc.) | 7 | 0.1% ❌ |
| Unaccounted | ~529 | ~7.8% ❓ |
| Total potential BC breaks | 71-600 | 1.0-8.8% |
Example breaking code:
<?php enum Status: string { case Active = 'active'; public static function values() { // ❌ Missing : array return array_column(self::cases(), 'value'); } } // Fatal error: Declaration of Status::values() must be compatible // with BackedEnum::values(): array ?>
While 91.2% of implementations already have the correct signature, breaking even 1-9% of the ecosystem (71-600 codebases) was deemed unacceptable for a convenience feature.
Solution chosen: Omit return type from interface. This allows all existing implementations to remain compatible while the native implementation still returns a proper array.
Future consideration: Stricter typing with : array could be reconsidered for PHP 9.0 (major version where BC breaks are more acceptable), with a deprecation period in PHP 8.x.
Initially considered making values() always native and non-overridable (like cases()/from()/tryFrom()).
Rejected because:
Conditional approach chosen instead: Provides the convenience method while avoiding widespread breakage from name collisions in existing code.
getValues(): More verbose, doesn't match cases() style
toArray(): Ambiguous - case objects or values? Names or values?
valueList(): Unnecessarily verbose
extractValues(): Too long, unclear
Decision: values() best matches:
cases()
Suggestion to use Status::$values instead of Status::values().
Rejected because:
cases(), from(), tryFrom() (all methods)Suggestion to provide standard library trait instead of native method.
Rejected because:
use statement in every enum (boilerplate persists)A common workaround today is:
<?php $values = array_column(Status::cases(), 'value'); ?>
This RFC proposes a dedicated method anyway for these reasons:
BackedEnum and call ::values() without requiring each project to ship its own helper/trait or repeat the idiom.cases()/from()/tryFrom() are well-known enum entry points; values() is an obvious companion, while the array_column idiom is not discoverable for many users.The goal is not to enable something impossible in userland, but to make the common pattern consistent and self-documenting.
: array) to ensure zero BC breaks