# [RFC] [Discussion] Add values() Method to BackedEnum

- Thread: https://externals.io/message/129186
- Original archive: https://news-web.php.net/php.internals/129186
- Started by Mikhail Savin on 2025-11-10T04:40:43+00:00
- Messages: 12
- RFC: Add values() Method to BackedEnum (Under discussion) — https://externals.io/rfc/add_values_method_to_backed_enum.md
- Reply by email to internals@lists.php.net
- 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

---

## #129186 — Mikhail Savin — 2025-11-10T04:40:43+00:00

URL: https://externals.io/message/129186#129186

Hi internals,

I've created an RFC to add a native values() method to BackedEnum:

https://wiki.php.net/rfc/add_values_method_to_backed_enum

== Summary ==

The RFC proposes adding BackedEnum::values() that returns an indexed array
of all backing values. The implementation uses conditional registration -
the native method is only added when the enum doesn't already define
values(), ensuring ZERO backward compatibility breaks.

Key points:
  * Native values() added automatically to new enums
  * Existing enums with custom values() continue working unchanged
  * Trait-based implementations are respected
  * Libraries can maintain their implementation for older PHP versions
  * Solves boilerplate problem (3,860+ implementations, ~24k-44k real usage)

Common use cases:
  * Database migrations: $table->enum('status', Status::values())
  * Form validation: in_array($input, Status::values())
  * API responses: ['allowed_values' => Status::values()]

== Implementation ==

Working implementation with conditional registration:
https://github.com/php/php-src/pull/20398

The engine checks if values() exists before registering the native version:
  - User-defined values() present? Use it.
  - No user-defined values()? Add native implementation.

== No BC Breaks ==

This approach ensures:
  * Existing code works unchanged (no migration needed)
  * Immediate benefit for new code (automatic values())
  * Gradual adoption possible (libraries can migrate at their pace)

Trade-off: Makes values() the only overridable enum method (unlike
cases/from/tryFrom). The RFC documents this as a pragmatic choice -
solving real problems without forced migrations.

== Questions ==

1. Is the conditional approach technically sound?
2. Is the API consistency trade-off acceptable given the zero BC breaks?
3. Any concerns with the implementation approach?
4. Should I implement some steps from "Future scope" of the rfc now?

Discussion period: 2 weeks minimum before voting.

Looking forward to your feedback!

Best regards,
Savin Mikhail

---

## #129192 — Valentin Udaltsov — 2025-11-10T20:55:35+00:00

URL: https://externals.io/message/129186#129192 · In reply to: #129186

пн, 10 нояб. 2025 г. в 08:44, Mikhail Savin <mikhail.d.savin@gmail.com>:

> Hi internals,
>
> I've created an RFC to add a native values() method to BackedEnum:
>
> https://wiki.php.net/rfc/add_values_method_to_backed_enum
>
> == Summary ==
>
> The RFC proposes adding BackedEnum::values() that returns an indexed array
> of all backing values. The implementation uses conditional registration -
> the native method is only added when the enum doesn't already define
> values(), ensuring ZERO backward compatibility breaks.
>
> Key points:
>   * Native values() added automatically to new enums
>   * Existing enums with custom values() continue working unchanged
>   * Trait-based implementations are respected
>   * Libraries can maintain their implementation for older PHP versions
>   * Solves boilerplate problem (3,860+ implementations, ~24k-44k real
> usage)
>
> Common use cases:
>   * Database migrations: $table->enum('status', Status::values())
>   * Form validation: in_array($input, Status::values())
>   * API responses: ['allowed_values' => Status::values()]
>
> == Implementation ==
>
> Working implementation with conditional registration:
> https://github.com/php/php-src/pull/20398
>
> The engine checks if values() exists before registering the native version:
>   - User-defined values() present? Use it.
>   - No user-defined values()? Add native implementation.
>
> == No BC Breaks ==
>
> This approach ensures:
>   * Existing code works unchanged (no migration needed)
>   * Immediate benefit for new code (automatic values())
>   * Gradual adoption possible (libraries can migrate at their pace)
>
> Trade-off: Makes values() the only overridable enum method (unlike
> cases/from/tryFrom). The RFC documents this as a pragmatic choice -
> solving real problems without forced migrations.
>
> == Questions ==
>
> 1. Is the conditional approach technically sound?
> 2. Is the API consistency trade-off acceptable given the zero BC breaks?
> 3. Any concerns with the implementation approach?
> 4. Should I implement some steps from "Future scope" of the rfc now?
>
> Discussion period: 2 weeks minimum before voting.
>
> Looking forward to your feedback!
>
> Best regards,
> Savin Mikhail
>

Hi, Mikhail!

Thank you for the RFC.

Consider this code if this RFC is accepted:

enum EnumWithUserDefinedValuesMethod: string
{
    case X = 'x';

    public static function values(): string
    {
        return 'values';
    }
}

function getBackedEnumValues(BackedEnum $enum): array
{
    return $enum::values();
}

getBackedEnumValues(EnumWithUserDefinedValuesMethod::X);


This code will suddenly break, because inside getBackedEnumValues I can
safely assume that BackedEnum::values() returns an array, since it's a part
of the interface contract.

However, EnumWithUserDefinedValuesMethod breaks the Liskov Substitution
Principle by defining a method with a non-compatible return type and gives
a runtime error.

What you've basically suggested is to ignore the LSP. This is not a good
idea.

--
Best regards, Valentin

---

## #129193 — Mikhail Savin — 2025-11-11T08:25:46+00:00

URL: https://externals.io/message/129186#129193 · In reply to: #129192

пн, 10 нояб. 2025 г. в 23:55, Valentin Udaltsov <udaltsov.valentin@gmail.com
>:

> пн, 10 нояб. 2025 г. в 08:44, Mikhail Savin <mikhail.d.savin@gmail.com>:
>
> > […]
>
> Hi, Mikhail!
>
> Thank you for the RFC.
>
> Consider this code if this RFC is accepted:
>
> enum EnumWithUserDefinedValuesMethod: string
> {
>     case X = 'x';
>
>     public static function values(): string
>     {
>         return 'values';
>     }
> }
>
> function getBackedEnumValues(BackedEnum $enum): array
> {
>     return $enum::values();
> }
>
> getBackedEnumValues(EnumWithUserDefinedValuesMethod::X);
>
>
> This code will suddenly break, because inside getBackedEnumValues I can
> safely assume that BackedEnum::values() returns an array, since it's a part
> of the interface contract.
>
> However, EnumWithUserDefinedValuesMethod breaks the Liskov Substitution
> Principle by defining a method with a non-compatible return type and gives
> a runtime error.
>
> What you've basically suggested is to ignore the LSP. This is not a good
> idea.
>
> --
> Best regards, Valentin
>


Thanks for raising the LSP concern. You’re right that today a userland enum
can define a values() with an arbitrary signature, and a helper like:

function getBackedEnumValues(BackedEnum $enum): array {
    return $enum::values();
}

could blow up at runtime if that user method returns a non-array.

The RFC addresses this by adding "public static function values(): array"
to the BackedEnum interface itself.
With that in place, an enum that defines an incompatible values() will fail
at compile time with a normal method-signature incompatibility, exactly
like it would for from()/tryFrom().

Here’s a .phpt demonstrating the intended behavior:

--TEST--
Backed enums: user-defined values() incompatible with interface signature
--FILE--
<?php

enum E: string {
    case A = 'a';

    // Intentional incompatibility: interface requires array return type
    public static function values(): string {
        return 'values';
    }
}

?>
--EXPECTF--
Fatal error: Declaration of E::values(): string must be compatible with
BackedEnum::values(): array in %s on line %d

Run with:
TEST_PHP_EXECUTABLE=sapi/cli/php sapi/cli/php -n run-tests.php -q
Zend/tests/enum/backed-values-user-defined-incompatible.phpt

So LSP isn’t being ignored; it’s enforced by the interface method.

That said, adding values() to BackedEnum is a source-level BC break for
codebases that already define conflicting values() on backed enums.

But, as we can see from GitHub searches that I posted in PR, most of the
implementations is exactly the same, so BC break is very small

---

## #129197 — drealecs@gmail.com — 2025-11-11T11:43:37+00:00

URL: https://externals.io/message/129186#129197 · In reply to: #129193

On Tue, Nov 11, 2025 at 10:29 AM Mikhail Savin <mikhail.d.savin@gmail.com>
wrote:

>
> That said, adding values() to BackedEnum is a source-level BC break for
> codebases that already define conflicting values() on backed enums.
>
> But, as we can see from GitHub searches that I posted in PR, most of the
> implementations is exactly the same, so BC break is very small
>

Do you think you can quantify this BC break: how many implementations are
having a different signature? And update the RFC "Impact on Ecosystem"
section?

Small note:
implementations usually use `array_column(self::cases(), 'value')` instead
of `array_map(static fn(self $c) => $c->value, self::cases())`.
and that's just normal, as it is 2x faster: https://3v4l.org/Q5AYg#v8.4.14
maybe mention it as well, or instead of the one you had it the examples, as
this is usually how it can be found in libraries.

-- 
Alex

---

## #129198 — Derick Rethans — 2025-11-11T11:50:21+00:00

URL: https://externals.io/message/129186#129198 · In reply to: #129193

> The RFC addresses this by adding "public static function values(): array"
> to the BackedEnum interface itself.
> With that in place, an enum that defines an incompatible values() will fail
> at compile time with a normal method-signature incompatibility, exactly
> like it would for from()/tryFrom().
> 
> Here’s a .phpt demonstrating the intended behavior:
> 
> --TEST--
> Backed enums: user-defined values() incompatible with interface signature
> --FILE--
> <?php
> 
> enum E: string {
>     case A = 'a';
> 
>     // Intentional incompatibility: interface requires array return type
>     public static function values(): string {
>         return 'values';
>     }
> }
> 
> ?>
> --EXPECTF--
> Fatal error: Declaration of E::values(): string must be compatible with
> BackedEnum::values(): array in %s on line %d

Right now, enums can have a "values()" function that can return anything 
I desire: an array of enum cases, or objects created from these, or 
strings, or M_PI.

If it has to be compatible with

	`BackedEnum::values(): array(int|string)`

(which is what your contract demands, even though it can't express the 
int|string part)

Then that is a BC break again.

cheers,
Derick

---

## #129205 — Mikhail Savin — 2025-11-12T11:35:52+00:00

URL: https://externals.io/message/129186#129205 · In reply to: #129198

вт, 11 нояб. 2025 г. в 14:50, Derick Rethans <derick@php.net>:

> 
>
> > […]
> fail
> > […]
>
> Right now, enums can have a "values()" function that can return anything
> I desire: an array of enum cases, or objects created from these, or
> strings, or M_PI.
>
> If it has to be compatible with
>
>         `BackedEnum::values(): array(int|string)`
>
> (which is what your contract demands, even though it can't express the
> int|string part)
>
> Then that is a BC break again.
>
> cheers,
> Derick


Hi, guys, thanks for feedback

Following the discussion with Alex and Derick, I've completed a
comprehensive BC
analysis. I need community input on a key decision: whether to include a
return
type in the interface declaration.

## The Two Options

**Option A: WITH return type (current PR)**
```php
interface BackedEnum {
    public static function values(): array;
}
```
- BC Breaks: 71-600 implementations (1.0-8.8%)
- Better type safety and IDE support
- Consistent with cases(): array

**Option B: WITHOUT return type (alternative)**
```php
interface BackedEnum {
    public static function values();
}
```
- BC Breaks: 0 (0%)
- All existing implementations compatible
- Can add `: array` in PHP 9.0

## BC Analysis Data

Total enums with values(): 6,800
- Compatible (: array): 6,200 (91.2%)
- Missing return type: 64 (0.9%)
- Wrong return types: 7 (0.1%)
- Unaccounted: ~529 (7.8%)

All GitHub search links: https://github.com/php/php-src/pull/20398

## Question for Community

Which approach should we take for PHP 8.6?

**Option A:** Accept 1-9% BC break for full type safety
**Option B:** Zero BC breaks, add typing in PHP 9.0

I'm inclined toward Option B (zero breaks for 8.6), but want to hear
community
preference before changing the implementation.

Thoughts?

---
Additional context:
- Implementation change is trivial (one line)
- Native implementation returns array regardless of interface
- Alex's array_column suggestion incorporated (+3.6k usages)
- All data verifiable via GitHub searches in PR


Best regards, Mikhail

---

## #129207 — Joseph Leedy — 2025-11-12T16:21:43+00:00

URL: https://externals.io/message/129186#129207 · In reply to: #129205

> ## Question for Community
>
> Which approach should we take for PHP 8.6?
>
> **Option A:** Accept 1-9% BC break for full type safety
> **Option B:** Zero BC breaks, add typing in PHP 9.0
>
> I'm inclined toward Option B (zero breaks for 8.6), but want to hear
> community
> preference before changing the implementation.
>
> Thoughts?

I think option B is the best way forward, in my opinion.

---

## #129211 — Faizan Akram Dar — 2025-11-12T18:42:04+00:00

URL: https://externals.io/message/129186#129211 · In reply to: #129205

>
> Hi, guys, thanks for feedback
>
> Following the discussion with Alex and Derick, I've completed a
> comprehensive BC
> analysis. I need community input on a key decision: whether to include a
> return
> type in the interface declaration.
>
> ## The Two Options
>
> **Option A: WITH return type (current PR)**
> ```php
> interface BackedEnum {
>     public static function values(): array;
> }
> ```
> - BC Breaks: 71-600 implementations (1.0-8.8%)
> - Better type safety and IDE support
> - Consistent with cases(): array
>
> **Option B: WITHOUT return type (alternative)**
> ```php
> interface BackedEnum {
>     public static function values();
> }
> ```
> - BC Breaks: 0 (0%)
> - All existing implementations compatible
> - Can add `: array` in PHP 9.0
>
> ## BC Analysis Data
>
> Total enums with values(): 6,800
> - Compatible (: array): 6,200 (91.2%)
> - Missing return type: 64 (0.9%)
> - Wrong return types: 7 (0.1%)
> - Unaccounted: ~529 (7.8%)
>
> All GitHub search links: https://github.com/php/php-src/pull/20398
>
> ## Question for Community
>
> Which approach should we take for PHP 8.6?
>
> **Option A:** Accept 1-9% BC break for full type safety
> **Option B:** Zero BC breaks, add typing in PHP 9.0
>
> I'm inclined toward Option B (zero breaks for 8.6), but want to hear
> community
> preference before changing the implementation.
>
> Thoughts?
>
> ---
> Additional context:
> - Implementation change is trivial (one line)
> - Native implementation returns array regardless of interface
> - Alex's array_column suggestion incorporated (+3.6k usages)
> - All data verifiable via GitHub searches in PR
>
>
> Best regards, Mikhail
>
>

Hi Mikhail,

I personally don't see any added value by adding such a method to enums at
this point when getting the equivalent results is already possible by a one
liner.

`array_column(EnumFqcn::cases(), 'value');`



Kind regards,
Faizan

---

## #129212 — Larry Garfield — 2025-11-12T19:01:06+00:00

URL: https://externals.io/message/129186#129212 · In reply to: #129205

> Hi, guys, thanks for feedback
>
> Following the discussion with Alex and Derick, I've completed a 
> comprehensive BC 
> analysis. I need community input on a key decision: whether to include 
> a return 
> type in the interface declaration.
>
> ## The Two Options
>
> **Option A: WITH return type (current PR)**
> ```php
> interface BackedEnum {
>     public static function values(): array;
> }
> ```
> - BC Breaks: 71-600 implementations (1.0-8.8%)
> - Better type safety and IDE support
> - Consistent with cases(): array
>
> **Option B: WITHOUT return type (alternative)**
> ```php
> interface BackedEnum {
>     public static function values();
> }
> ```
> - BC Breaks: 0 (0%)
> - All existing implementations compatible
> - Can add `: array` in PHP 9.0
>
> ## BC Analysis Data
>
> Total enums with values(): 6,800
> - Compatible (: array): 6,200 (91.2%)
> - Missing return type: 64 (0.9%)
> - Wrong return types: 7 (0.1%)
> - Unaccounted: ~529 (7.8%)
>
> All GitHub search links: https://github.com/php/php-src/pull/20398
>
> ## Question for Community
>
> Which approach should we take for PHP 8.6?
>
> **Option A:** Accept 1-9% BC break for full type safety
> **Option B:** Zero BC breaks, add typing in PHP 9.0
>
> I'm inclined toward Option B (zero breaks for 8.6), but want to hear community 
> preference before changing the implementation.
>
> Thoughts?
>
> ---
> Additional context:
> - Implementation change is trivial (one line)
> - Native implementation returns array regardless of interface
> - Alex's array_column suggestion incorporated (+3.6k usages)
> - All data verifiable via GitHub searches in PR 

Isn't this what the #[ReturnTypeWillChange] attribute was intended for?

cf: https://wiki.php.net/rfc/internal_method_return_types

That seems like an option C?  It's technically a BC break, but people can just drop an attribute on it to disable the return type check until 9.0.  We did the same for the various internal methods, as noted in that RFC.

--Larry Garfield

---

## #129213 — Niels Dossche — 2025-11-12T19:25:01+00:00

URL: https://externals.io/message/129186#129213 · In reply to: #129212

> Isn't this what the #[ReturnTypeWillChange] attribute was intended for?
> 
> cf: https://wiki.php.net/rfc/internal_method_return_types
> 
> That seems like an option C?  It's technically a BC break, but people can just drop an attribute on it to disable the return type check until 9.0.  We did the same for the various internal methods, as noted in that RFC.

Parameter checks are not disabled by that attribute, so the BC problem remains.

---

## #129260 — Mikhail Savin — 2025-11-16T09:44:28+00:00

URL: https://externals.io/message/129186#129260 · In reply to: #129213

ср, 12 нояб. 2025 г. в 22:26, Niels Dossche <dossche.niels@gmail.com>:

>
>
> 
> > […]
> can just drop an attribute on it to disable the return type check until
> 9.0.  We did the same for the various internal methods, as noted in that
> RFC.
>
> Parameter checks are not disabled by that attribute, so the BC problem
> remains.
>

Hi everyone,

Based on your feedback, I’ve updated both the PR and the RFC by removing
the return type declaration from the interface. Thanks for the input.

---

## #129408 — tim@bastelstu.be — 2025-11-23T15:19:53+00:00

URL: https://externals.io/message/129186#129408 · In reply to: #129211

Hi


> I personally don't see any added value by adding such a method to enums at
> this point when getting the equivalent results is already possible by a one
> liner.
> 
> `array_column(EnumFqcn::cases(), 'value');`

I agree with that. I feel that BackedEnums are already over-used to the 
point where they just act as “fancy strings” instead of a proper data 
type on their own and I don't think we should further encourage treating 
enums as fancy strings.

In addition to that, a custom implementation of the method is trivially 
done as shown by Faizan and doesn't even need to be made available by 
the author of the enum but can be written on demand when having all 
values (without the corresponding names) is necessary.

And even when there is not a BC break right now, by not actually 
fixating the signature in the interface, it would be with PHP 9.0 and 
from then on it would (needlessly) block another method on enums that 
folks might otherwise be interested in using, since the name is very 
generic. Even when never adding it to the interface - static methods on 
interfaces are odd - it would break user expectations that some enum 
methods can be redefined and others can't.

Best regards
Tim Düsterhus
