====== PHP RFC: Add values() Method to BackedEnum ======
* Version: 1.2
* Date: 2025-11-09 (Updated: 2025-11-16)
* Author: Savin Mikhail, mikhail.d.savin@gmail.com
* Status: Under Discussion
* Target Version: PHP 8.6
* Implementation: https://github.com/php/php-src/pull/20398
* Discussion: https://externals.io/message/129186
===== Introduction =====
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:
* about 7,460 direct implementations of extracting values from ''self::cases()'';
* about 2,900 occurrences of traits that define ''function values()'' (often applied to many enums), which suggests the real usage is significantly higher.
* a conservative estimate is 20,000-40,000 total usages once trait reuse is accounted for.
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.
Common use cases include:
* Database schema definitions: ''$table->enum('status', Status::values())''
* Form validation: ''Choice(callback: [Status::class, 'values'])''
* API responses: ''['allowed_statuses' => Status::values()]''
===== Proposal =====
Add the following method to the ''BackedEnum'' interface:
Semantics when the native implementation is used:
* Returns an indexed array containing the backing values of all enum cases.
* Order is the same as ''cases()'' (declaration order).
* Available only for backed enums (not for pure enums).
===== Backward Compatibility =====
The RFC aims to avoid method name collisions with existing code bases by combining two design choices:
* The interface method is declared without a return type.
* The engine registers the native implementation only if the enum does not already declare a ''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.
===== Why not a one-liner? =====
Today users can write:
However, a native ''values()'' method:
* is discoverable in IDE autocomplete next to ''cases()'', ''from()'' and ''tryFrom()'';
* standardizes a very common helper that is currently implemented in many incompatible ways (method name, trait name, behavior);
* reduces boilerplate in frameworks, libraries, and user code.
The large number of existing implementations suggests that the ecosystem repeatedly needs this helper.
===== Proposed PHP Version(s) =====
Next PHP 8.x (PHP 8.6)
===== RFC Impact =====
==== To the Ecosystem ====
IDEs, Language servers, static analyzers, polyfills might need to update their stubs to reflect docblock from updated BackedEnum interface
==== To Existing Extensions ====
No impact.
==== To SAPIs ====
No impact.
===== Open Issues =====
The following points were raised during discussion and are explicitly called out for voters to consider:
* Whether conditional registration (allowing existing user-defined ''values()'' to take precedence) is an acceptable trade-off versus strict consistency with ''cases()/from()/tryFrom()''.
* Whether the interface should eventually gain a concrete return type (e.g. '': array'') in a future major version.
* Whether a future deprecation path toward a non-overridable native method is desirable (out of scope for this RFC).
===== Future Scope =====
==== Optional: Future Convergence on Mandatory values() ====
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()''
**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 =====
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.h''
* register method name via interned string (''Zend/zend_string.h'')
* implement and register the handler in ''Zend/zend_enum.c'' with conditional registration
* add tests under ''Zend/tests/enum/'' and update reflection expectations under ''ext/reflection/tests/''
===== Alternatives Considered =====
* Different method name (''getValues()'', ''toArray()''): rejected as less consistent with ''cases()'' and less aligned with existing practice.
* Declaring ''values(): array'' in the interface: rejected due to potential compatibility issues with existing userland signatures.
* Always-native, non-overridable ''values()'': rejected because it would cause hard failures for existing enums that already declare ''values()''.
* Virtual/static property style (''Status::$values''): rejected as inconsistent with existing enum APIs and not supported by the language.
===== Voting =====
This is a Yes/No vote, requiring a 2/3 majority.
Voting will start later
===== References =====
* Discussion: https://externals.io/message/129186
* Implementation PR: https://github.com/php/php-src/pull/20398
The results of GitHub usage examples you can find in PR
Related RFCs:
* Enumerations (PHP 8.1): https://wiki.php.net/rfc/enumerations
* Allow static properties in enums: https://wiki.php.net/rfc/enum_allow_static_properties
* Auto-implement Stringable for string backed enums: https://wiki.php.net/rfc/auto-implement_stringable_for_string_backed_enums
* Fetch properties of enums in const expressions: https://wiki.php.net/rfc/fetch_property_in_const_expressions
===== Rejected Features =====
==== Interface WITH Return Type ('': array'') ====
An alternative implementation was considered where the interface would explicitly declare an '': array'' return type:
**Advantages:**
* Full type safety in the interface contract
* Better IDE inference and autocomplete
* Consistent with modern PHP practices
* Matches ''cases(): array'' signature
**Why 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:**
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.
==== Mandatory Native Implementation (Breaking Change) ====
Initially considered making ''values()'' always native and non-overridable (like ''cases()''/''from()''/''tryFrom()'').
**Rejected because:**
* Would break ~24,000-44,000 existing enum instances
* Disproportionate impact for the benefit provided
* Libraries particularly affected (cannot easily drop old PHP version support)
* Internals feedback indicated BC break too large
**Conditional approach chosen instead:** Provides the convenience method while avoiding widespread breakage from name collisions in existing code.
==== Alternative Method Names ====
**''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:
* Existing community usage (3,860+ examples use this name)
* Parallel naming with ''cases()''
* Simplicity and clarity
==== Virtual/Magic Properties ====
Suggestion to use ''Status::$values'' instead of ''Status::values()''.
**Rejected because:**
* Enums cannot have static properties (language restriction)
* No mechanism for static virtual properties exists
* Inconsistent with ''cases()'', ''from()'', ''tryFrom()'' (all methods)
* Would require complex engine changes
==== User-land Trait in Standard Library ====
Suggestion to provide standard library trait instead of native method.
**Rejected because:**
* Requires ''use'' statement in every enum (boilerplate persists)
* Not automatically available (discoverability issue)
* Fragmentation - multiple competing trait implementations exist
* Conditional native approach provides better UX
==== Why not use array_column(self::cases(), 'value')? ====
A common workaround today is:
This RFC proposes a dedicated method anyway for these reasons:
* **Standardization for libraries/frameworks:** public APIs can accept ''BackedEnum'' and call ''::values()'' without requiring each project to ship its own helper/trait or repeat the idiom.
* **Discoverability:** ''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.
* **Readability:** intent is clearer at call sites (especially in schema/validation code).
The goal is not to enable something impossible in userland, but to make the common pattern consistent and self-documenting.
===== Changelog =====
* **2025-11-09:** Initial RFC published with conditional implementation approach
* **2025-11-09:** Announced on internals@lists.php.net
* **2025-11-16:** Updated to clarify interface has no return type (not '': array'') to ensure zero BC breaks
* **2026-01-18:** Prepared RFC to voting phase, emphasize motivation
* (Future updates will be listed here)