пн, 10 нояб. 2025 г. в 23:55, Valentin Udaltsov <[email protected] >:
> пн, 10 нояб. 2025 г. в 08:44, Mikhail Savin <[email protected]>: > >> 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 > 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
