This RFC proposes to add a new get_declared_enums()
function in the next PHP 8.x release and to make the behaviour of the related functions consistent by:
class_exists()
function in the next PHP 8.x and removing support for enum from class_exists()
in PHP 9.0.get_declared_classes()
function and to remove enums from the return value of get_declared_classes()
in PHP 9.0.There are four different OO structures in PHP:
For all four, a *_exists()
function is available, i.e. class_exists()
, interface_exists()
, trait_exists()
and enum_exists()
.
But only for three out of the four, a get_declared_*()
function exists.
There is get_declared_classes()
, get_declared_interfaces()
, get_declared_traits()
, but no get_declared_enums()
.
This proposal intends to remedy this by adding a get_declared_enums()
function in the next PHP 8.x release.
Furthermore, the behaviour of the get_declared_classes()
and class_exists()
functions is inconsistent, as get_declared_classes()
currently also returns the names of enums and class_exists()
returns true
for both classes as well as enums.
To illustrate:
Function/Given | Class | Interface | Trait | Enum |
---|---|---|---|---|
class_exists() | true/false | false | false | true/false |
interface_exists() | false | true/false | false | false |
trait_exists() | false | false | true/false | false |
enum_exists() | false | false | false | true/false |
Demo: https://3v4l.org/1PWFo
Function/Returns | Classes | Interfaces | Traits | Enums |
---|---|---|---|---|
get_declared_classes() | yes | no | no | yes |
get_declared_interfaces() | no | yes | no | no |
get_declared_traits() | no | no | yes | no |
get_declared_enums() (proposed) | no | no | no | yes |
Demo: https://3v4l.org/0ub6I
This means that these functions do not act as the “silos” they appear to be, which can be a cause of bugs and other surprises.
To remedy this, a deprecation notice will be added when class_exists()
is used with an enum name.
As of PHP 9.0, class_exists()
will no longer act on enum names and return false
for those.
As for get_declared_classes()
, the proposal is to soft deprecate the return value including enum names, and to change the return value to no longer include enum names as of PHP 9.0.
According to Larry Garfield (one of the authors of the enumerations RFC):
I can't remember the last time I usedget_declared_classes()
[...], so when we were working on enums it never occurred to us to think about it. It wasn't a deliberate decision to omit, as far as I recall.
In other words: it appears to have been just an oversight. Not an intentional design choice.
For the lack of a get_declared_enums()
function, it is fortunate that get_declared_classes()
includes enums in its return value, as otherwise there would be no way, at this time, to retrieve the list of enum symbols.
However, once the get_declared_enums()
function would be introduced, this behaviour becomes inconsistent and surprising.
After all, internally, classes, interfaces, traits and enums are all considered classes, so why should get_declared_classes()
include enums, but not interfaces and traits ?
Changing the return value of get_declared_classes()
, however, constitutes a BC-break for the (limited) set of users consciously using get_declared_classes()
to also retrieve the names of declared enum symbols.
With that in mind, changing the return value of get_declared_classes()
is proposed for PHP 9.0.
Along the same lines as the above: interface and traits are also classes internally, but calling class_exists()
on an interface or trait name will return false
, while calling it on an enum name will return true
.
This is inconsistent and surprising behaviour which this RFC intends to fix.
The function itself is not deprecated, but the return value will change to not include enums.
As, when the function is called, there is no way to know how the return value will be used, it seems inappropriate to throw a deprecation notice.
A warning about the upcoming change in the return value of get_declared_classes()
in both the UPGRADING
document, the migration guide, and on the manual page for get_declared_classes()
.
Claude Pache brought up the following alternative approach on the mailing list:
const SIMPLE_CLASS = 1; const ABSTRACT_CLASS = 2; const ANONYMOUS_CLASS = 4; const ENUM = 8; function get_declared_classes(int $type = SIMPLE_CLASS | ABSTRACT_CLASS | ANONYMOUS_CLASS | ENUM): array { /* ... */ }
This approach could even be extrapolated to the following:
const SIMPLE_CLASSES = 1; const ABSTRACT_CLASSES = 2; const ANONYMOUS_CLASSES = 4; const INTERFACES = 8; const TRAITS = 16; const ENUMS = 32; function get_declared_symbols(?int $type = null, bool $categorize = false): array { /* ... */ }
This would then allow for deprecating - and eventually removing - the get_declared_classes()
, get_declared_interfaces()
and get_declared_traits()
methods in favour of the unified get_declared_symbols()
method.
Deprecating and removing the get_declared_classes()
, get_declared_interfaces()
and get_declared_traits()
methods would, however, be more disruptive to userland than the current proposal, which is why the current proposal prevailed.
The introduction of the new function means the name can no longer be used in userland code (without a function_exists()
wrapper).
A GitHub code search yielded zero matches on public GitHub repositories that define a function with the same name.
If so desired, it is possible to polyfill the new get_declared_enums()
function (props Christoph M. Becker and Nicolas Grekas):
function get_declared_enums(): array { $enums = []; $exts = get_loaded_extensions(false); foreach ($exts as $ext) { $re = new ReflectionExtension($ext); $classes = $re->getClasses(); foreach ($classes as $class) { if ($class->isEnum()) { $enums[] = $class->name; } } } return $enums; }
function get_declared_enums(): array { return array_filter(get_declared_classes(), 'enum_exists'); }
The enum_exists()
function has been included in PHP since PHP 8.1, when enums were introduced.
Code specifically intended to check whether an enum exists, will (or should) be using the enum_exists()
function already.
There may be some code, which intentionally uses class_exists()
as a convenient catch-all to check for both classes as well as enums, such code could be adjusted per the below to be cross-version compatible (and avoid the deprecation notice):
-if (class_exists($name)) {} +if (enum_exists($name) || class_exists($name)) {}
As of PHP 9.0, existing code which explicitly relies on get_declared_classes()
to retrieve both class, as well as enum symbol names, will break.
If so desired, this can be worked around as follows in a cross-version compatible manner to maintain the old behaviour:
$classes = get_declared_classes(); if (function_exists('get_declared_enums')) { $enums = get_declared_enums(); $classes = array_values(array_unique(array_merge($classes, $enums))); }
Alternatively, the new (PHP 9.0) behaviour could be emulated in a cross-version compatible manner, like so:
$classes = get_declared_classes(); $enums = []; if (function_exists('get_declared_enums')) { $enums = get_declared_enums(); } if (PHP_VERSION_ID < 90000) { if (!empty($enums)) { // PHP 8.5 < 9.0: only remove enums from $classes. $classes = array_diff($classes, $enums); } elseif (function_exists('enum_exists')) { // PHP 8.1 - 8.4: remove enums from $classes and add to $enums. $classes = array_values(array_filter( $classes, function ($name) use (&$enums) { if (enum_exists($name, false)) { $enums[] = $name; return false; } return true; } )); } }
Tools like PHPCompatibility, PHPStan and Exakat, could possibly/probably flag up code which would potentially be affected by the PHP 9.0 removal of enums from the return value of get_declared_classes()
.
get_declared_enums()
function.class_exists()
on enum symbols.get_declared_classes()
(to not include enums).class_exists()
(to return false
for enums instead of true
).None
None
None
None
None
The behaviour of the get_declared_interfaces()
and get_declared_traits()
functions remains unchanged.
The behaviour of the interface_exists()
, trait_exists()
and enum_exists()
functions remains unchanged.
The Reflection API for enums also remains unchanged.
include
/require
. These are not addressed in this RFC and are left as a potential future language enhancement.get_declared_*()
functions are not terribly memory efficient. Cutting down on memory fragmentation/garbage collector churn caused by these functions is outside the scope of the current proposal.Each vote would need a 2/3 majority. Voting started on 2024-XX-XX YY:YY UTC and closed on 2024-XX-XX YY:YY UTC.
* The third vote is conditional on the first vote passing.
get_declared_enums()
for 8.x: https://github.com/php/php-src/pull/15443class_exists()
with enums: https://github.com/Ayesh/php-src/pull/14After the project is implemented, this section should contain
None at this time.