rfc:add_values_method_to_backed_enum

PHP RFC: Add values() Method to BackedEnum

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.

<?php
 
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
    case Archived = 'archived';
}
 
var_dump(Status::values());
// ['active', 'inactive', 'archived']
 
?>

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:

<?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:

  • 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:

<?php
$values = array_column(Status::cases(), 'value');
?>

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()

<?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

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

The results of GitHub usage examples you can find in PR

Related RFCs:

Rejected Features

Interface WITH Return Type ('': array'')

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:

  • 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:

<?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.

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:

<?php
$values = array_column(Status::cases(), 'value');
?>

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)
rfc/add_values_method_to_backed_enum.txt · Last modified: by msavin