# RFC: Add values() Method to BackedEnum

- Status: Under discussion
- Authors: Savin Mikhail, mikhail.d.savin@gmail.com
- Date: 2025-11-09 (Updated: 2025-11-16)
- Version: 1.2
- Target: PHP 8.6
- Implementation: https://github.com/php/php-src/pull/20398
- Original: https://wiki.php.net/rfc/add_values_method_to_backed_enum
- Page: https://externals.io/rfc/add_values_method_to_backed_enum
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Discussions on the mailing list

- [\[RFC\] \[Discussion\] Add values() Method to BackedEnum](https://externals.io/message/129186.md) — started on 2025-11-10, 12 messages
- [\[VOTE\] Add BackedEnum::values()](https://externals.io/message/129770.md) — started on 2026-01-18, 6 messages

---

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

- 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://externals.io/rfc/enumerations>
- Allow static properties in enums: [https://externals.io/rfc/enum\_allow\_static\_properties](https://externals.io/rfc/enum_allow_static_properties)
- Auto-implement Stringable for string backed enums: [https://wiki.php.net/rfc/auto-implement\_stringable\_for\_string\_backed\_enums](https://externals.io/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](https://externals.io/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:

```php
<?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
<?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
<?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)
