пт, 7 нояб. 2025 г. в 07:08, Valentin Udaltsov <[email protected] >:
> чт, 6 нояб. 2025 г., 19:30 Larry Garfield <[email protected]>: > >> On Wed, Nov 5, 2025, at 10:09 PM, Mikhail Savin wrote: >> > Hi internals, >> > >> > I would like to propose adding a native values() method to the >> BackedEnum >> > interface that returns an array of all backing values. Before creating a >> > formal RFC, I'm seeking feedback on the concept and approach. >> > >> > == Summary == >> > >> > The proposal adds: >> > >> > interface BackedEnum { >> > public static function values(): array; >> > } >> > >> > This would allow: >> > >> > enum Status: string { >> > case Active = 'active'; >> > case Inactive = 'inactive'; >> > } >> > >> > Status::values(); // ['active', 'inactive'] >> > >> > == Motivation == >> > >> > This pattern is extremely common in the wild. Based on GitHub code >> search: >> > >> > * ~3,860+ direct implementations of this exact pattern >> > * ~20,000-40,000 estimated real usage when accounting for shared >> traits >> > * Used in major frameworks: Symfony core (TypeIdentifier.php), >> > Laravel ecosystem >> > * Documented by PHP.net: The manual itself shows EnumValuesTrait as >> > an example >> >> Correction: The manual does not show an EnumValuesTrait that I can find. >> There is a *comment* in the manual that includes this method, but that's >> not part of the manual proper, and frankly 90% of comments in the manual >> should be removed. (cf: >> https://www.php.net/manual/en/language.enumerations.traits.php#129250) >> >> > Common use cases: >> > * Database migrations: $table->enum('status', Status::values()) >> > * Form validation: $validator->rule('status', 'in', Status::values()) >> > * API responses: ['allowed_statuses' => Status::values()] >> > >> > == Implementation == >> > >> > I have a working implementation with tests: >> > https://github.com/php/php-src/pull/20398 >> > >> > The implementation: >> > * Mirrors the existing cases() method structure >> > * Extracts the value property from each case >> > * Returns an indexed array (0, 1, 2, ...) >> > * Only available on BackedEnum, not UnitEnum >> > * All tests pass >> >> I am unclear why this is a major advantage over >> array_column(Status::cases(), 'value'); >> >> > == Backward Compatibility - Important Discussion Point == >> > >> > This is a breaking change. Enums that already define a values() method >> > will fail with: >> > >> > Fatal error: Cannot redeclare BackedEnum::values() >> > >> > Based on ecosystem research: >> > * ~24,000-44,000 enum instances will break >> > * All implementations are functionally identical to what's being >> proposed >> > * Migration is mechanical: just delete the user-defined method >> >> This is a hard-stop. There are hundreds of thousands of packages in the >> wild that need to support multiple PHP versions. Nearly all packaglist >> packages (which I presume is where you're drawing the research from; either >> that or GitHub which will give a similar result set) support at least two >> consecutive versions, if not 4, 5, or 6. >> >> A hard break like this would essentially mean the packages containing >> those 40,000 enums would be unable to support both PHP 8.5 and 8.6 at the >> same time. That's simply not an acceptable impact on the ecosystem, >> regardless of how nice the feature may or may not be. >> >> --Larry Garfield >> > > We could add a virtual $values property. Since enum properties are not > allowed in userland, it will not break any existing code. > > — > Valentin > Hi all, Thank you for the thoughtful feedback. Based on the discussion so far, the consensus seems to be: "the feature is useful, but the BC break is too large." To address this, I've adjusted the proposal so that user code is allowed to redeclare values() on backed enums. This keeps existing projects working unchanged while providing the native implementation for new code. The native values() will only be added when not already defined: ```c if (!zend_hash_exists(&ce->function_table, ZSTR_KNOWN(ZEND_STR_VALUES))) { ... } ``` Result: * Zero BC break - existing code unchanged * New enums get values() automatically * Libraries can maintain their implementation for older PHP support Trade-off: I recognize this makes values() the only overridable enum intrinsic (unlike cases/from/tryFrom). I'll document this clearly and add tests to lock down the behavior. If needed, we can deprecate user-defined values() in a later 8.x and make it an error in PHP 9.0. Questions: 1. Is allowing values() override technically acceptable? 2. Is documenting the inconsistency sufficient? 3. Should we add deprecation? 4. Can I submit RFC for this feature? 5. Should I rather implement it via virtual property, as Valentin suggested above? I updated the PR, and also added a few tests for this behavior. Thoughts? Best regards, Savin Mikhail
