====== PHP RFC: Add values() Method to BackedEnum ======
* Version: 1.0
* Date: 2025-11-09
* Author: Savin Mikhail, mikhail.d.savin@gmail.com
* Status: Under Discussion
* Target Version: PHP 8.6
* Implementation: https://github.com/php/php-src/pull/20398
* Discussion: https://externals.io/message/XXXXX
===== Introduction =====
This RFC proposes adding a native ''values()'' method to the ''BackedEnum'' interface that returns an indexed array of all backing values. The native implementation is added **conditionally** - only when the enum doesn't already define its own ''values()'' method, ensuring **zero backward compatibility breaks**.
string(6) "active" [1]=> string(8) "inactive" [2]=> string(8) "archived" }
?>
**Common use cases:**
* Database schema definitions: ''$table->enum('status', Status::values())''
* Form validation: ''$validator->rule('status', 'in', Status::values())''
* API responses: ''['allowed_statuses' => Status::values()]''
===== Proposal =====
Add a native ''values()'' static method to the ''BackedEnum'' interface that is conditionally registered based on whether the user has already defined it:
==== Conditional Registration ====
The native implementation is only registered when the enum **does not** already define a ''values()'' method:
$c->value, self::cases());
sort($values);
return $values;
}
}
Priority::values(); // User's implementation: [1, 10] (sorted)
?>
This approach ensures:
* **Zero BC breaks** - existing code with ''values()'' continues working unchanged
* **Immediate benefit** - new enums automatically get ''values()''
* **Library compatibility** - libraries can maintain their implementation for older PHP versions
==== Behavior ====
When the native implementation is used:
* **Returns:** An indexed array (keys: 0, 1, 2, ...) containing the backing values of all enum cases
* **Order:** Declaration order (same as ''cases()'')
* **Type:** ''array'' for int-backed enums, ''array'' for string-backed enums
* **Empty enums:** Returns ''[]''
* **Availability:** Only on ''BackedEnum'', not on ''UnitEnum'' (pure enums)
==== Examples ====
**Basic usage (native implementation):**
int(1) [1]=> int(5) [2]=> int(10) }
?>
**Database migrations:**
enum('status', OrderStatus::values());
// ['pending', 'processing', 'completed', 'cancelled']
});
?>
**Form validation:**
['required', 'in:' . implode(',', Country::values())]
]);
?>
**API responses:**
json([
'available_plans' => Feature::values(),
// ['basic', 'pro', 'enterprise']
]);
?>
**User-defined implementation (respects custom behavior):**
strtoupper($c->value),
self::cases()
);
}
}
var_dump(Color::values());
// array(3) { [0]=> string(3) "RED" [1]=> string(5) "GREEN" [2]=> string(4) "BLUE" }
?>
**Library compatibility example:**
$c->value, self::cases());
}
}
// Works in both PHP 8.4 and PHP 8.6+
var_dump(LibraryEnum::values());
// array(2) { [0]=> string(4) "opt1" [1]=> string(4) "opt2" }
?>
**Trait-based implementations work unchanged:**
$c->value, self::cases());
}
}
enum Status: string {
use EnumValues; // User's trait takes precedence
case Draft = 'draft';
case Published = 'published';
}
// Uses trait implementation, native is not added
var_dump(Status::values());
?>
**Empty enum edge case:**
===== Backward Incompatible Changes =====
**This RFC introduces ZERO backward compatibility breaks.**
The conditional registration approach ensures that:
* **Existing enums** with custom ''values()'' methods continue working unchanged
* **Trait-based** implementations are respected
* **Libraries** can maintain their implementations for older PHP version support
* **No migration** is required for any existing code
==== How It Works ====
During enum registration, the engine checks if a ''values()'' method already exists:
// Simplified concept (actual implementation in zend_enum.c)
if (user_defined_values_exists(enum_class)) {
// User has values() - respect their implementation
return;
}
// No user-defined values() - register native implementation
register_native_values(enum_class);
This means:
* **PHP 8.4/8.5 code** with custom ''values()'' works identically in **PHP 8.6**
* **New enums** automatically get ''values()'' without any code
* **Gradual migration** is possible - libraries can remove their implementation when they drop PHP 8.5 support
==== Impact on Ecosystem ====
**Positive impacts:**
* ~3,860+ enums with custom ''values()'' - **continue working** (no changes needed)
* ~20,000-40,000 enums via traits - **continue working** (no changes needed)
* New enums - **automatically get** ''values()'' without boilerplate
* **Zero migration effort** for existing code
**Optional cleanup opportunity:**
Over time, projects can optionally remove redundant ''values()'' implementations when they drop support for PHP < 8.6:
$c->value, self::cases());
}
}
// PHP 8.6+: Can remove (but not required)
enum Status: string {
case Active = 'active';
// Native values() available automatically
}
?>
==== Trade-off: API Consistency ====
This approach makes ''values()'' **the only enum method that can be user-defined**:
| Method | User-definable? |
|--------|----------------|
| ''cases()'' | ❌ No (always native) |
| ''from()'' | ❌ No (always native) |
| ''tryFrom()'' | ❌ No (always native) |
| ''values()'' | ✅ Yes (conditional) |
**Rationale for this trade-off:**
* Avoids **all** backward compatibility breaks
* Provides **immediate benefit** without ecosystem disruption
* Allows **gradual adoption** - libraries can migrate at their own pace
* **Practical** over theoretical consistency - solves real problem (24k-44k instances of boilerplate) without forcing changes
**Future path to full consistency:**
If desired, a future RFC could:
1. Emit ''E_DEPRECATED'' for user-defined ''values()'' in PHP 8.x
2. Make it non-overridable in PHP 9.0
3. Achieve full consistency with other enum methods
This RFC deliberately prioritizes **pragmatic value delivery** over **perfect API consistency**.
===== Proposed PHP Version(s) =====
Next PHP 8.x (PHP 8.6)
This is a feature addition with zero BC breaks, appropriate for a minor version.
===== RFC Impact =====
==== To the Ecosystem ====
**Positive impacts:**
* **IDEs/LSPs:** Native method appears in autocomplete for enums without custom ''values()''
* **Static Analyzers:** Can infer ''values()'' availability more reliably
* **Frameworks:** No changes needed, but can gradually simplify helpers/traits
* **Documentation:** Single standard approach simplifies teaching
* **Libraries:** No forced migrations, can maintain compatibility across PHP versions
**No negative impacts:**
* Zero breaking changes
* All existing code continues working
* Optional cleanup can happen gradually
==== To Existing Extensions ====
No impact to existing extensions. This is a core enum feature with no extension dependencies.
==== To SAPIs ====
No impact. This is a language-level feature that behaves identically across all SAPIs (CLI, FPM, embedded, etc.).
===== Open Issues =====
None. The conditional approach addresses the BC concerns raised during initial discussion.
===== Future Scope =====
==== Optional: Future Convergence on Mandatory values() ====
If the community later prefers full consistency (non-overridable ''values()'' like other enum methods):
**Phase 1 (PHP 8.x):** Emit ''E_DEPRECATED'' for user-defined ''values()''
**Phase 2 (PHP 9.0):** Make user-defined ''values()'' an error
This gives the ecosystem years to migrate while eventually achieving full API consistency.
**This future scope is NOT part of the current RFC** - just documenting the possibility.
==== Potential Enhancement: Array Keys ====
Future RFC could add optional parameter to control array keys:
'active', 'Inactive' => 'inactive']
Status::values(preserveKeys: false); // ['active', 'inactive'] (default)
?>
This RFC deliberately keeps the API simple with indexed arrays matching ''cases()'' behavior.
==== Additional Helper Methods ====
Future RFCs could add related methods:
* ''names()'': Return array of case names
* ''toArray()'': Return associative array of name => value pairs
These are intentionally excluded from this RFC to keep scope focused.
===== Voting Choices =====
This is a simple yes/no vote requiring 2/3 majority as it's a language feature addition.
Vote will open 2 weeks after RFC announcement and remain open for 2 weeks.
* Yes
* No
===== Patches and Tests =====
**Pull Request:** https://github.com/php/php-src/pull/20398
**Implementation includes:**
* Core implementation in ''Zend/zend_enum.c'' with conditional registration logic
* Stub files updated (''zend_enum.stub.php'')
* Comprehensive test coverage (9+ test files) including:
* Native implementation (when user doesn't define ''values()'')
* User-defined implementation (when user defines ''values()'')
* Trait-based implementation
* Reflection behavior for both cases
* Edge cases (empty enums, order preservation, etc.)
* Documentation in NEWS and UPGRADING
**Key implementation detail:**
The conditional check happens during enum registration:
// In zend_enum_register_funcs() - simplified
zend_function *existing = zend_hash_str_find_ptr(
&ce->function_table, "values", sizeof("values")-1
);
if (existing && existing->common.scope == ce) {
// User defined values() on this enum - respect it
return;
}
// No user-defined values() - register native implementation
zend_internal_function *values_function = ...;
zend_enum_register_func(ce, ZEND_STR_VALUES, values_function);
All tests pass. Implementation is ready for merge pending RFC approval.
===== Implementation =====
After the RFC is approved, this section will contain:
* Version merged into: PHP 8.6.0
* Git commit: (link will be added)
* PHP manual entry: (link will be added)
===== References =====
**Research and Evidence:**
* GitHub code search: ~3,860 direct implementations found
* Estimated real usage: ~20,000-40,000 (accounting for trait pattern)
* Symfony core usage: ''symfony/symfony/src/Symfony/Component/TypeInfo/TypeIdentifier.php''
* PHP.net manual: Documents ''EnumValuesTrait'' pattern
* Internals discussion: (link will be added after initial email)
**Search queries performed:**
* https://github.com/search?q=language:PHP+"array_map(fn($case)+=>+$case->value,+self::cases())"
* https://github.com/search?q=language:PHP+"return+array_map"+"self::cases()"+"->value"
* https://github.com/search?q=language:PHP+"function+values()"+"return+array_map"+"self::cases()"
**Prior Art:**
* **TypeScript:** ''Object.values(EnumType)''
* **Python:** ''[e.value for e in EnumType]''
* **myclabs/php-enum** (legacy): Had ''values()'' method (4,900 stars)
**Related RFCs:**
* PHP 8.1 Enumerations: https://wiki.php.net/rfc/enumerations
* No previous RFC for ''values()'' method
===== Rejected Features =====
==== Mandatory Native Implementation (Breaking Change) ====
Initially considered making ''values()'' always native and non-overridable (like ''cases()''/''from()''/''tryFrom()'').
**Rejected because:**
* Would break ~24,000-44,000 existing enum instances
* Disproportionate impact for the benefit provided
* Libraries particularly affected (cannot easily drop old PHP version support)
* Internals feedback indicated BC break too large
**Conditional approach chosen instead:** Provides benefit without breaking changes.
==== Alternative Method Names ====
**''getValues()''**: More verbose, doesn't match ''cases()'' style
**''toArray()''**: Ambiguous - case objects or values? Names or values?
**''valueList()''**: Unnecessarily verbose
**''extractValues()''**: Too long, unclear
**Decision:** ''values()'' best matches:
* Existing community usage (3,860+ examples use this name)
* Parallel naming with ''cases()''
* Simplicity and clarity
==== Virtual/Magic Properties ====
Suggestion to use ''Status::$values'' instead of ''Status::values()''.
**Rejected because:**
* Enums cannot have static properties (language restriction)
* No mechanism for static virtual properties exists
* Inconsistent with ''cases()'', ''from()'', ''tryFrom()'' (all methods)
* Would require complex engine changes
==== User-land Trait in Standard Library ====
Suggestion to provide standard library trait instead of native method.
**Rejected because:**
* Requires ''use'' statement in every enum (boilerplate persists)
* Not automatically available (discoverability issue)
* Fragmentation - multiple competing trait implementations exist
* Conditional native approach provides better UX
==== array_column() Alternative ====
Suggestion that ''array_column(Status::cases(), 'value')'' is sufficient.
**Rejected because:**
* Not discoverable for newcomers
* Less readable than dedicated method
* Doesn't solve standardization problem
* Evidence shows community strongly prefers explicit method (3,860+ implementations)
==== Different Signature ====
Considered allowing customization: ''values(sorted: true)'' or ''values(unique: true)''.
**Rejected because:**
* Adds complexity for rare use cases
* Users can easily pipe through ''array_unique()'' or ''sort()'' if needed
* Keeps API consistent with simple ''cases()''
==== Deprecation Period for User-Defined values() ====
Considered emitting ''E_DEPRECATED'' for user-defined ''values()'' immediately.
**Rejected because:**
* Unnecessary - the conditional approach works well
* Would create noise without benefit
* Can be revisited in future if full API consistency is desired
===== Changelog =====
* **2025-11-09:** Initial RFC published with conditional implementation approach
* **2025-11-09:** Announced on internals@lists.php.net
* (Future updates will be listed here)