Table of Contents

PHP RFC: Add values() Method to BackedEnum

Introduction

This RFC proposes adding a native values() method to the BackedEnum interface that returns an indexed array of all backing values. The native implementation is added conditionally - only when the enum doesn't already define its own values() method, ensuring zero backward compatibility breaks.

<?php
 
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
    case Archived = 'archived';
}
 
// Automatically available - no manual implementation needed:
var_dump(Status::values());
// array(3) { [0]=> string(6) "active" [1]=> string(8) "inactive" [2]=> string(8) "archived" }
 
?>

Common use cases:

Proposal

Add a native values() static method to the BackedEnum interface that is conditionally registered based on whether the user has already defined it:

<?php
 
interface BackedEnum extends UnitEnum
{
    /**
     * Returns an indexed array of all backing values for the enum cases.
     * 
     * This method is automatically available unless the enum defines its own
     * values() method, in which case the user-defined implementation is used.
     *
     * @return int[]|string[]
     */
    public static function values(): array;
}
 
?>

Conditional Registration

The native implementation is only registered when the enum does not already define a values() method:

<?php
 
// Case 1: No user-defined values() - native implementation added automatically
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
}
 
Status::values(); // Native implementation: ['active', 'inactive']
 
// Case 2: User-defined values() - native implementation NOT added, user's version is used
enum Priority: int {
    case Low = 1;
    case High = 10;
 
    public static function values(): array {
        // Custom implementation - maybe sorted
        $values = array_map(fn($c) => $c->value, self::cases());
        sort($values);
        return $values;
    }
}
 
Priority::values(); // User's implementation: [1, 10] (sorted)
 
?>

This approach ensures:

Behavior

When the native implementation is used:

Examples

Basic usage (native implementation):

<?php
 
enum Priority: int {
    case Low = 1;
    case Medium = 5;
    case High = 10;
}
 
var_dump(Priority::values());
// array(3) { [0]=> int(1) [1]=> int(5) [2]=> int(10) }
 
?>

Database migrations:

<?php
 
enum OrderStatus: string {
    case Pending = 'pending';
    case Processing = 'processing';
    case Completed = 'completed';
    case Cancelled = 'cancelled';
}
 
// Laravel migration
Schema::create('orders', function (Blueprint $table) {
    $table->enum('status', OrderStatus::values());
    // ['pending', 'processing', 'completed', 'cancelled']
});
 
?>

Form validation:

<?php
 
enum Country: string {
    case USA = 'us';
    case Canada = 'ca';
    case Mexico = 'mx';
}
 
// Symfony validation
use Symfony\Component\Validator\Constraints as Assert;
 
class Address {
    #[Assert\Choice(callback: [Country::class, 'values'])]
    public string $countryCode;
}
 
// Laravel validation
$validator = Validator::make($data, [
    'country' => ['required', 'in:' . implode(',', Country::values())]
]);
 
?>

API responses:

<?php
 
enum Feature: string {
    case BasicPlan = 'basic';
    case ProPlan = 'pro';
    case EnterprisePlan = 'enterprise';
}
 
// OpenAPI / JSON Schema
return response()->json([
    'available_plans' => Feature::values(),
    // ['basic', 'pro', 'enterprise']
]);
 
?>

User-defined implementation (respects custom behavior):

<?php
 
enum Color: string {
    case Red = 'red';
    case Green = 'green';
    case Blue = 'blue';
 
    // Custom implementation - returns uppercase values
    public static function values(): array {
        return array_map(
            fn($c) => strtoupper($c->value),
            self::cases()
        );
    }
}
 
var_dump(Color::values());
// array(3) { [0]=> string(3) "RED" [1]=> string(5) "GREEN" [2]=> string(4) "BLUE" }
 
?>

Library compatibility example:

<?php
 
// Library code that needs to support PHP 8.4+
enum LibraryEnum: string {
    case Option1 = 'opt1';
    case Option2 = 'opt2';
 
    // Defined for PHP 8.4/8.5 compatibility
    // In PHP 8.6+, this is used instead of native (no conflict)
    public static function values(): array {
        return array_map(fn($c) => $c->value, self::cases());
    }
}
 
// Works in both PHP 8.4 and PHP 8.6+
var_dump(LibraryEnum::values());
// array(2) { [0]=> string(4) "opt1" [1]=> string(4) "opt2" }
 
?>

Trait-based implementations work unchanged:

<?php
 
trait EnumValues {
    public static function values(): array {
        return array_map(fn($c) => $c->value, self::cases());
    }
}
 
enum Status: string {
    use EnumValues; // User's trait takes precedence
 
    case Draft = 'draft';
    case Published = 'published';
}
 
// Uses trait implementation, native is not added
var_dump(Status::values());
 
?>

Empty enum edge case:

<?php
 
enum EmptyEnum: string {}
 
var_dump(EmptyEnum::values());
// array(0) { }
 
?>

Backward Incompatible Changes

This RFC introduces ZERO backward compatibility breaks.

The conditional registration approach ensures that:

How It Works

During enum registration, the engine checks if a values() method already exists:

// Simplified concept (actual implementation in zend_enum.c)
if (user_defined_values_exists(enum_class)) {
    // User has values() - respect their implementation
    return;
}
 
// No user-defined values() - register native implementation
register_native_values(enum_class);

This means:

Impact on Ecosystem

Positive impacts:

Optional cleanup opportunity: Over time, projects can optionally remove redundant values() implementations when they drop support for PHP < 8.6:

<?php
 
// PHP 8.4/8.5: Need custom implementation
enum Status: string {
    case Active = 'active';
 
    public static function values(): array {
        return array_map(fn($c) => $c->value, self::cases());
    }
}
 
// PHP 8.6+: Can remove (but not required)
enum Status: string {
    case Active = 'active';
    // Native values() available automatically
}
 
?>

Trade-off: API Consistency

This approach makes values() the only enum method that can be user-defined:

Method User-definable?
------------------------
cases() ❌ No (always native)
from() ❌ No (always native)
tryFrom() ❌ No (always native)
values() ✅ Yes (conditional)

Rationale for this trade-off:

Future path to full consistency: If desired, a future RFC could:

1. Emit ''E_DEPRECATED'' for user-defined ''values()'' in PHP 8.x
2. Make it non-overridable in PHP 9.0
3. Achieve full consistency with other enum methods

This RFC deliberately prioritizes pragmatic value delivery over perfect API consistency.

Proposed PHP Version(s)

Next PHP 8.x (PHP 8.6)

This is a feature addition with zero BC breaks, appropriate for a minor version.

RFC Impact

To the Ecosystem

Positive impacts:

No negative impacts:

To Existing Extensions

No impact to existing extensions. This is a core enum feature with no extension dependencies.

To SAPIs

No impact. This is a language-level feature that behaves identically across all SAPIs (CLI, FPM, embedded, etc.).

Open Issues

None. The conditional approach addresses the BC concerns raised during initial discussion.

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.

Potential Enhancement: Array Keys

Future RFC could add optional parameter to control array keys:

<?php
// Hypothetical future enhancement (NOT part of this RFC)
Status::values(preserveKeys: true);  // ['Active' => 'active', 'Inactive' => 'inactive']
Status::values(preserveKeys: false); // ['active', 'inactive'] (default)
?>

This RFC deliberately keeps the API simple with indexed arrays matching cases() behavior.

Additional Helper Methods

Future RFCs could add related methods:

These are intentionally excluded from this RFC to keep scope focused.

Voting Choices

This is a simple yes/no vote requiring 2/3 majority as it's a language feature addition.

Vote will open 2 weeks after RFC announcement and remain open for 2 weeks.

Add BackedEnum::values() method with conditional registration as described in this RFC?
Real name Yes No
Count: 0 0
This poll will close on 2025-12-07 00:00:00 UTC.

Patches and Tests

Pull Request: https://github.com/php/php-src/pull/20398

Implementation includes:

Key implementation detail:

The conditional check happens during enum registration:

// In zend_enum_register_funcs() - simplified
zend_function *existing = zend_hash_str_find_ptr(
    &ce->function_table, "values", sizeof("values")-1
);
 
if (existing && existing->common.scope == ce) {
    // User defined values() on this enum - respect it
    return;
}
 
// No user-defined values() - register native implementation
zend_internal_function *values_function = ...;
zend_enum_register_func(ce, ZEND_STR_VALUES, values_function);

All tests pass. Implementation is ready for merge pending RFC approval.

Implementation

After the RFC is approved, this section will contain:

References

Research and Evidence:

Search queries performed:

Prior Art:

Related RFCs:

Rejected Features

Mandatory Native Implementation (Breaking Change)

Initially considered making values() always native and non-overridable (like cases()/from()/tryFrom()).

Rejected because:

Conditional approach chosen instead: Provides benefit without breaking changes.

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:

Virtual/Magic Properties

Suggestion to use Status::$values instead of Status::values().

Rejected because:

User-land Trait in Standard Library

Suggestion to provide standard library trait instead of native method.

Rejected because:

array_column() Alternative

Suggestion that array_column(Status::cases(), 'value') is sufficient.

Rejected because:

Different Signature

Considered allowing customization: values(sorted: true) or values(unique: true).

Rejected because:

<?php
// Users can customize as needed
$sorted = Status::values();
sort($sorted);
 
$unique = array_unique(Status::values());
?>

Deprecation Period for User-Defined values()

Considered emitting E_DEPRECATED for user-defined values() immediately.

Rejected because:

Changelog