Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@savinmikhail
Copy link

@savinmikhail savinmikhail commented Nov 5, 2025

Add values() Method to BackedEnum

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.

The interface declares the method without a return type to ensure zero backward compatibility breaks with 6,800+ existing implementations.

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_column(self::cases(), 'value');
    }
}

This pattern appears widely across GitHub and frameworks, often implemented directly or via traits (which hides usage from code search).

Quantitative Evidence

Direct implementations (comprehensive GitHub code search as of 2025-11-12):

Pattern Results Search Link
array_column(self::cases(), 'value') 3,600 search
array_map(fn($case) => $case->value, self::cases()) 330 search
return array_map + self::cases() + ->value 1,900 search
return array_map + fn($case) => $case->value 324 search
function values() + return array_map + self::cases() 784 search
function toArray() + array_map + self::cases() + ->value 236 search
function getValues() + array_map + self::cases() 196 search
function values() + foreach loop + ->value 90 search
Total direct implementations ~7,460

Implementation distribution:

  • array_column pattern: 3,600 (48%) - most popular ✨
  • array_map patterns: 2,554 (34%)
  • Other patterns: 1,306 (18%)

Trait pattern multiplier (2,900 results):

  • Many projects factor this into a trait (e.g., EnumValuesTrait) and then use it in dozens of enums
  • Direct search counts significantly understate total usage
  • Conservative estimate: 20,000-40,000 total implementations in the ecosystem

Qualitative evidence (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
{
    /**
     * Returns an indexed array of all backing values for the enum cases.
     *
     * @return int[]|string[]
     */
    public static function values();
}

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

The interface declares the method without a return type to ensure zero backward compatibility breaks:

interface BackedEnum extends UnitEnum
{
    /**
     * Returns an indexed array of all backing values for the enum cases.
     *
     * @return int[]|string[]
     */
    public static function values();  // ← NO return type in interface
}

Characteristics:

  • Zero BC breaks - all existing implementations remain compatible
  • ✅ Native implementation returns array - proper type at runtime
  • ✅ Docblock provides type information for static analysis
  • ✅ Allows implementations to specify any return type or none
  • ✅ All 6,800+ existing userland implementations continue working unchanged

Backward Compatibility Analysis

Comprehensive GitHub search analysis (2025-11-12) of existing values() implementations:

Category Count % Search Link
Total enums with values() 6,800 100% search
Compatible: : array return type 6,200 91.2% search
Missing return type 64 0.9% search
Incompatible: : string 4 0.06% search
Incompatible: : int 0 0% search
Incompatible: : Closure 0 0% search
Incompatible: : iterable 1 0.01% search
Incompatible: : Iterator 2 0.03% search
Incompatible: : Generator 0 0% search
Returns case objects (not values) 0 0% search
Unaccounted (~529 remaining) ~529 ~7.8%

Backward Compatibility

BC Breaks:ZERO (0%)

By omitting the return type from the interface, all existing implementations remain compatible. The native implementation always returns array, but user-defined implementations can use any return type:

// All of these are compatible:
public static function values(): array { }      // ✅ Compatible
public static function values() { }             // ✅ Compatible
public static function values(): string { }     // ✅ Compatible (though semantically wrong)

Comprehensive GitHub search analysis (2025-11-12) found 6,800 existing values() implementations:

  • 91.2% (6,200) already have : array return type
  • 0.9% (64) have no return type
  • 0.1% (7) have incompatible return types (: string, : Iterator, etc.)
  • All continue working without changes

Implementation Details

Engine changes (Zend)

Add stub and arginfo:

  • Zend/zend_enum.stub.php: add BackedEnum::values() signature without return type
    public static function values();
  • Zend/zend_enum_arginfo.h: regenerated (committed)

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(), extracting the value property from each case
    • Register for all backed enums
    • Native implementation always returns array regardless of interface declaration

Tests

Reflection tests:

  • ext/reflection/tests/BackedEnum_values_reflection.phpt - ensure method appears
  • ext/reflection/tests/ReflectionEnum_toString_backed_int.phpt - update toString
  • ext/reflection/tests/ReflectionEnum_toString_backed_string.phpt - update toString

Enum behavior tests:

  • Zend/tests/enum/backed-values-int.phpt - int-backed enum values
  • Zend/tests/enum/backed-values-string.phpt - string-backed enum values
  • Zend/tests/enum/backed-values-empty.phpt - empty enum edge case
  • Zend/tests/enum/backed-values-order.phpt - declaration order preservation
  • Zend/tests/enum/backed-values-not-on-pure.phpt - pure enums don't have values()
  • Zend/tests/enum/backed-values-ignore-regular-consts.phpt - regular constants ignored
  • Zend/tests/enum/backed-values-user-defined.phpt - user-defined values() overrides native
  • Zend/tests/enum/backed-values-user-defined-incompatible.phpt - different return types allowed

Documentation in-tree

  • NEWS: announce the feature under Core
  • UPGRADING: List under "New Features" (no BC section needed - zero breaks)

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 entry for BackedEnum::values()

Performance

Native implementation provides:

  • O(n) complexity over number of cases
  • Similar performance to array_column(self::cases(), 'value')
  • No userland call overhead

Benchmark (100,000 iterations):

array_column: 0.012s
array_map:    0.035s (2.9x slower)
native:       ~0.012s (similar to array_column)

Reference: https://3v4l.org/Q5AYg

Note: array_column is the most popular pattern (48% of implementations, 3,600 results) and is ~3x faster than array_map.

Alternatives Considered

Different method name

  • getValues(), valueList(): More verbose, doesn't match cases() style
  • toArray(): Ambiguous - case objects or values? Names or values?

Decision: values() best matches:

  • Existing community usage (7,460+ examples)
  • Parallel naming with cases()
  • Simplicity and clarity

Virtual/magic properties

Status::$values instead of Status::values()

Rejected:

  • Enums cannot have static properties
  • Inconsistent with cases(), from(), tryFrom() (all methods)

Impact on Ecosystem

Positive impacts

  • 100% of implementations (6,800+) continue working unchanged
  • Zero BC breaks - no migration needed
  • Zero ecosystem disruption
  • ✅ Native implementation returns proper array type
  • ✅ New enums automatically get values() without boilerplate
  • ✅ Standardization reduces ecosystem fragmentation
  • ✅ Improved discoverability for new developers
  • ✅ Simplified documentation (single standard approach)
  • ✅ Can add stricter typing in PHP 9.0 if desired

Rejected Features

Interface WITH Return Type (: array)

An alternative approach was considered where the interface would explicitly declare : array return type:

interface BackedEnum extends UnitEnum
{
    public static function values(): array;  // ← WITH return type
}

Advantages:

  • Full type safety in interface contract
  • Better IDE inference and autocomplete
  • Consistent with modern PHP practices
  • Matches cases(): array signature

Why rejected:

This approach would cause BC breaks affecting 1.0-8.8% of existing implementations (71-600 codebases):

Category Count %
Total enums with values() 6,800 100%
Compatible: : array return type 6,200 91.2%
Missing return type 64 0.9%
Incompatible: other types 7 0.1%
Unaccounted ~529 ~7.8% ❓

Example breaking code:

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 was deemed unacceptable for a convenience feature. The chosen approach (no return type) provides the same functionality with zero disruption.

This stricter typing could be reconsidered for PHP 9.0, where major BC breaks are more acceptable, with a deprecation period in PHP 8.x.

Prior Art

No previous RFCs proposing a BackedEnum::values() method were found.

Related RFCs

Review of the PHP RFC index shows enum-related proposals:

None address adding a convenience method returning backing values.

Other languages

  • TypeScript: Object.values(EnumType)
  • Python: [e.value for e in EnumType]
  • myclabs/php-enum (legacy): Had values() method (4,900 stars, pre-8.1)

Checklist

  • Engine implementation
  • Arginfo generated and committed
  • Tests added (enum + reflection + user-defined overrides)
  • NEWS and UPGRADING updated
  • Zero BC breaks confirmed
  • Comprehensive BC analysis with GitHub searches
  • Manual docs (php/doc-en) PR to be submitted after review

Links

Thank you for reviewing!

@noelmaHelloCSE
Copy link

noelmaHelloCSE commented Nov 6, 2025

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
Copy link
Author

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

@savinmikhail
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.

@savinmikhail
Copy link
Author

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

@ndossche
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.

@savinmikhail
Copy link
Author

@TimWolla Could you please grant RFC karma to user msavin on wiki.php.net?
You marked this PR with the “Requires RFC” label, and I’ve already emailed internals requesting karma, but haven’t received a response yet. Thanks.

@TimWolla
Copy link
Member

TimWolla commented Nov 9, 2025

Could you please grant RFC karma to user msavin on wiki.php.net?

I'm afraid I don't have the necessary Wiki permissions.

@ndossche
Copy link
Member

ndossche commented Nov 9, 2025

cc @iluuu1994 @cmb69 Can one of you please grant RFC karma? See https://externals.io/message/129107#129127

@iluuu1994
Copy link
Member

Yes. Though note that karma requests should happen in new threads. They are easily missed otherwise.

@savinmikhail
Copy link
Author

savinmikhail commented Nov 10, 2025

Thanks, guys, for the help

I created the rfc: https://wiki.php.net/rfc/add_values_method_to_backed_enum
And started new email thread: https://externals.io/message/129186

@savinmikhail
Copy link
Author

Updated the pr description according to discussion on RFC about 2 points: usage of array_column (found out +3.6k usages) and BC break in return type declaration

@savinmikhail
Copy link
Author

I updated rfc and pr by removing interface return type declaration based on feedback from internals

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants