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
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
== 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
The break is justified because:
- Behavior is unchanged - native implementation does exactly what users
already implemented - Migration is trivial - simply remove the redundant method
- Precedent exists - PHP 8.1 native enums broke myclabs/php-enum
(4.9k stars) similarly - Long-term benefit - standardization, discoverability, elimination
of boilerplate - No alternative - virtual properties are technically infeasible;
different name doesn't match community expectations
== Questions for Discussion ==
-
BC break acceptability: Given the scope and straightforward migration,
is this break acceptable? -
Method name: values() matches community usage (3,860+ examples) and
parallels cases(). Alternatives like getValues() or toArray() were
considered but seem inferior. Thoughts? -
Target version: Currently targeting PHP 8.6 (master branch). Is this
appropriate? -
Deprecation period: Should we emit
E_DEPRECATEDin 8.5 and fatal error
in 9.0? Or accept the break immediately? (Deprecation adds engine
complexity and delays benefit.)
== Prior Art ==
- Symfony: Uses this pattern in core components
- PHP.net Manual: Documents EnumValuesTrait approach
- TypeScript: Object.values(Enum)
- Python: [e.value for e in Enum]
- myclabs/php-enum: Had values() method (4.9k stars)
== Next Steps ==
If feedback is generally positive, I will:
- Request RFC karma
- Create formal RFC on wiki.php.net
- Address any concerns raised in this discussion
- Move to formal voting after discussion period
== Implementation Details ==
For those interested in the technical details, the PR includes:
- Core implementation in zend_enum.c
- Stub file updates
- Comprehensive test coverage (9 test files)
- Reflection support
- Documentation in NEWS and UPGRADING
PR: https://github.com/php/php-src/pull/20398
Looking forward to your feedback!
Best regards, Savin Mikhail
GitHub: @savinmikhail
Hi Savin,
Thanks for sharing your idea.
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 exampleCommon use cases:
* Database migrations: $table->enum('status', Status::values())
* Form validation: $validator->rule('status', 'in', Status::values())
* API responses: ['allowed_statuses' => Status::values()]
I agree, this is a common feature I would like as well.
== 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 methodThe break is justified because:
1. Behavior is unchanged - native implementation does exactly what users
already implemented
2. Migration is trivial - simply remove the redundant method
3. Precedent exists - PHP 8.1 native enums broke myclabs/php-enum
(4.9k stars) similarly
4. Long-term benefit - standardization, discoverability, elimination
of boilerplate
5. No alternative - virtual properties are technically infeasible;
different name doesn't match community expectations
Here I don't agree!
PHP application especially libraries and frameworks very often have to
support multiple PHP versions. They can't just remove an already
implemented function and drop support for all previous PHP versions at once.
So either, the new function will not be final - to allow getting
overwritten by current implementations - in this case you would need to
run another analytics to check for naming clashes with different
incompatible signature or behavior.
Or it needs to kind of deprecation period or another name.
Just my 2 cents
Marc
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 exampleCommon 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/20398The 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
== 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
The break is justified because:
- Behavior is unchanged - native implementation does exactly what users
already implemented- Migration is trivial - simply remove the redundant method
- Precedent exists - PHP 8.1 native enums broke myclabs/php-enum
(4.9k stars) similarly- Long-term benefit - standardization, discoverability, elimination
of boilerplate- No alternative - virtual properties are technically infeasible;
different name doesn't match community expectations== Questions for Discussion ==
BC break acceptability: Given the scope and straightforward migration,
is this break acceptable?Method name: values() matches community usage (3,860+ examples) and
parallels cases(). Alternatives like getValues() or toArray() were
considered but seem inferior. Thoughts?Target version: Currently targeting PHP 8.6 (master branch). Is this
appropriate?Deprecation period: Should we emit
E_DEPRECATEDin 8.5 and fatal error
in 9.0? Or accept the break immediately? (Deprecation adds engine
complexity and delays benefit.)== Prior Art ==
- Symfony: Uses this pattern in core components
- PHP.net Manual: Documents EnumValuesTrait approach
- TypeScript: Object.values(Enum)
- Python: [e.value for e in Enum]
- myclabs/php-enum: Had values() method (4.9k stars)
== Next Steps ==
If feedback is generally positive, I will:
- Request RFC karma
- Create formal RFC on wiki.php.net http://wiki.php.net/
- Address any concerns raised in this discussion
- Move to formal voting after discussion period
== Implementation Details ==
For those interested in the technical details, the PR includes:
- Core implementation in zend_enum.c
- Stub file updates
- Comprehensive test coverage (9 test files)
- Reflection support
- Documentation in NEWS and UPGRADING
PR: https://github.com/php/php-src/pull/20398
Looking forward to your feedback!
Best regards, Savin Mikhail
GitHub: @savinmikhail
Hey,
Thanks for this! I would love to see this in core.
- Method name: values() matches community usage (3,860+ examples) and
parallels cases(). Alternatives like getValues() or toArray() were
considered but seem inferior. Thoughts?
values()
BC break acceptability: Given the scope and straightforward migration,
is this break acceptable?Target version: Currently targeting PHP 8.6 (master branch). Is this
appropriate?Deprecation period: Should we emit
E_DEPRECATEDin 8.5 and fatal error
in 9.0? Or accept the break immediately? (Deprecation adds engine
complexity and delays benefit.)
Perhaps the smoothest way would be to allow from 8.6 redeclaring values(), in the same time deprecate it and break it later (9.0)?
Cheers