Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add values() Method to BackedEnum #20398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
savinmikhail wants to merge 10 commits into php:master
base: master
Choose a base branch
Loading
from savinmikhail:enum-values/clean-rewrite

Conversation

@savinmikhail
Copy link

@savinmikhail savinmikhail commented Nov 5, 2025
edited
Loading

Summary

Introduce a native BackedEnum::values() static method that returns the list of all backing values (int|string) of a backed enum’s cases in declaration order. This eliminates boilerplate commonly implemented across projects and aligns with the existing cases() API.

Target: master (PHP 8.6)

Motivation

The ecosystem repeatedly implements the same pattern to produce an array of enum values:

enum Status: string {
 case Draft = 'draft';
 case Published = 'published';
 // Boilerplate implemented in countless codebases today
 public static function values(): array {
 return array_map(fn($case) => $case->value, self::cases());
 }
}

This pattern appears widely across GitHub and frameworks, often implemented directly or via traits (which hides usage from code search). A conservative summary of real-world evidence:

Quantitative (code search + trait multiplier):

Pattern GitHub search Results
array_map(fn($case) => $case->value, self::cases()) click ~330
return array_map + self::cases() + ->value click ~1,900
return array_map + fn($case) => $case->value click ~324
function values() + return array_map + self::cases() click ~784
function toArray() + array_map + self::cases() + ->value click ~236
function getValues() + array_map + self::cases() click ~196
function values() + foreach + self::cases() + [] = $ + ->value click ~90
Total ~3,860

Trait pattern multiplier:

  • Many projects factor this into a trait (e.g., EnumValuesTrait) and then use it in dozens of enums, so direct search counts significantly understate usage.
  • PHP manual example shows trait approach

Qualitative (frameworks & libraries):

Providing a native values() method:

  • Removes boilerplate and fragmentation (different traits/implementations).
  • Improves discoverability and consistency (parallels cases() nicely).
  • Simplifies migrations from legacy enum libraries and existing project traits.

Proposal

Add the following method to the BackedEnum interface:

interface BackedEnum extends UnitEnum
{
 public static function values(): array;
}

Semantics:

  • Available only for backed enums (int|string). Not available on unit enums.
  • Returns a 0-based, indexed array of the backing values in declaration order.
  • For int-backed enums, returns array<int>; for string-backed, array<string>.

Examples:

enum Priority: int { case Low = 1; case High = 10; }
Priority::values(); // [1, 10]
enum Color: string { case Red = 'red'; case Blue = 'blue'; }
Color::values(); // ['red', 'blue']

Implementation Details

Engine changes (Zend):

  • Add stub and arginfo
    • Zend/zend_enum.stub.php: add BackedEnum::values() signature.
    • Zend/zend_enum_arginfo.h: regenerated (committed) to include arginfo_class_BackedEnum_values.
  • Add interned string identifier
    • Zend/zend_string.h: add ZEND_STR_VALUES ("values").
  • Implement and register the method
    • Zend/zend_enum.c:
      • Implement zend_enum_values_func(), mirroring cases() but extracting the value property of each case object.
      • Register for backed enums in both the internal method table and zend_enum_register_funcs() (user classes).

Tests:

  • Reflection: ensure the method appears on backed enums
    • ext/reflection/tests/BackedEnum_values_reflection.phpt
    • Update toString expectations for backed enums:
      • ext/reflection/tests/ReflectionEnum_toString_backed_int.phpt
      • ext/reflection/tests/ReflectionEnum_toString_backed_string.phpt
  • Enum behavior tests
    • Zend/tests/enum/backed-values-int.phpt
    • Zend/tests/enum/backed-values-string.phpt
    • Zend/tests/enum/backed-values-empty.phpt
    • Zend/tests/enum/backed-values-order.phpt
    • Zend/tests/enum/backed-values-not-on-pure.phpt
    • Zend/tests/enum/backed-values-ignore-regular-consts.phpt

Documentation in-tree:

  • NEWS: announce the feature under Core.
  • UPGRADING: list under "New Features".

Manual (php/doc-en) to be added in a separate PR:

  • BackedEnum interface page: values() signature, description, examples.
  • Enumerations guide: mention values() alongside cases()/from()/tryFrom().
  • Migration guide for 8.6: add an entry for BackedEnum::values().

Backward Compatibility

No BC break.

  • The engine registers the native BackedEnum::values() only if the enum does not already declare a userland static values() method.
  • If an enum (or a trait it uses) defines values(), that method is preserved and continues to work unchanged.
  • Reflection continues to show a values() method; either the native one or the user-defined one.
  • Unit enums remain unaffected; they do not expose values().

Compatibility notes:

  • Projects may optionally remove duplicate userland implementations and rely on the native method, but no migration is required.
  • Behavior of existing custom values() implementations is preserved; the engine does not override or redeclare them.

Thanks, @vudaltsov, for highlighting the risks—resolved by conditional registration.

Optional Cleanup

Projects that wish to standardize on the native API can remove their custom values() implementations and rely on BackedEnum::values(); behavior will remain the same.

Mitigations Considered

  • Conditional registration (chosen): skip registering the native method if userland already defines values(); preserves BC while providing a standard API where missing.
  • Different method name (e.g., getValues(), valueList()): avoids collision but diverges from established conventions and symmetry with cases().
  • Phased rollout (deprecation first): unnecessary given the conditional approach above.

Performance

  • O(n) over the number of cases, similar to:
array_map(fn($case) => $case->value, self::cases())
  • Native implementation avoids userland call overhead and enables future internal optimizations if needed.

Alternatives Considered

  • Keep status quo: leaves significant, repeated boilerplate across the ecosystem.
  • Traits / helpers in userland: remains fragmented and non-discoverable.
  • Different naming (toArray(), getValues()): ambiguous or verbose; values() best matches existing community conventions and parallels cases().

Prior Art

No previous RFCs proposing a BackedEnum::values() method were found.
A review of the PHP RFC index shows several enum-related proposals:

However, none of them address adding a convenience method returning the list of backing values.
This proposal introduces it for the first time.

Checklist

  • Engine implementation
  • Arginfo generated and committed
  • Tests added (enum + reflection)
  • NEWS and UPGRADING updated
  • Manual docs (php/doc-en) PR to be submitted after review

Thank you for reviewing!

vudaltsov, Erol314, ClaudioEyzaguirre, xdendev, Lokusok, iTearo, NickSdot, ivan-kulkov22, Georgy1984, ionov-e, and 7 more reacted with thumbs up emoji
Copy link

noelmaHelloCSE commented Nov 6, 2025
edited
Loading

Great and thorough study — the addition of this method seems highly anticipated, and I believe it's underrated because one of the most common approaches uses array_column :)

savinmikhail reacted with heart emoji

Copy link
Author

I emailed the internals, now I'm waiting for the feedback

Copy link
Author

Based on the feedback from the internals so far: consensus seems to be "the feature is useful, but the BC break is too large." To address that, I’ve adjusted the proposal so that user code is allowed to redeclare values() on backed enums. This keeps existing projects working unchanged.

I recognize this makes values() the only intrinsic enum method that can be overridden, unlike cases()/from()/tryFrom(). If we proceed with this transitional approach, I’ll document the inconsistency, add tests to lock down the behavior, and ensure Reflection output is predictable.

If, over time, the community prefers to consolidate on a single intrinsic (no redeclaration), we can introduce a deprecation phase in a later minor and make redeclaration an error in the next major. For now, the goal is to deliver the utility of values() without forcing immediate code changes.

Copy link
Author

savinmikhail commented Nov 7, 2025
edited
Loading

@vudaltsov suggested to use virtual property $values for that, I am not sure would it be better than the current implementation (with redeclaring values() method), cuz it would be less consistent. Any thoughts?

Copy link
Member

ndossche commented Nov 7, 2025

@vudaltsov suggested to use virtual property $values for that, I am not sure would it be better than the current implementation (with redeclaring values() method), cuz it would be less consistent. Any thoughts?

I understand the reasoning, but this would indeed be weird from a user point of view.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ndossche ndossche ndossche left review comments

@kocsismate kocsismate Awaiting requested review from kocsismate kocsismate is a code owner

@dstogov dstogov Awaiting requested review from dstogov dstogov is a code owner

@DanielEScherzer DanielEScherzer Awaiting requested review from DanielEScherzer DanielEScherzer is a code owner

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /