Should there be a `get_declared_enums()` function ?

php.internals

Unnamed Person

2 years ago
L.S., I just noticed the following, which struck me as weird/inconsistent: There are four different OO structures in PHP: 1. Classes 2. Interfaces 3. Traits 4. Enums For all four, an `*_exists()` function is available, i.e. `class_exists()`, `interface_exists()`, `trait_exists()` and `enum_exists()` [2]. 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()` [2]. I'm aware that enums are internally considered classes and that `get_declared_classes()` will retrieve them [1], but the same could be said about interfaces and traits, yet they do have their own `get_declared_*()` function. Should a `get_declared_enums()` function be added ? And should the `get_declared_classes()` function be adjusted to exclude enums ? I did check the enum RFC [3], but I couldn't find any mention or discussion about this in the RFC. Smile, Juliette 1: https://3v4l.org/0ub6I 2: https://www.php.net/manual/en/ref.classobj.php 3: https://wiki.php.net/rfc/enumerations

Larry Garfield

2 years ago
On Wed, Aug 14, 2024, at 8:51 PM, Juliette Reinders Folmer wrote:
> L.S., > > I just noticed the following, which struck me as weird/inconsistent: > > There are four different OO structures in PHP: > 1. Classes > 2. Interfaces > 3. Traits > 4. Enums > > For all four, an `*_exists()` function is available, i.e. > `class_exists()`, `interface_exists()`, `trait_exists()` and > `enum_exists()` [2]. > > 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()` [2]. > > I'm aware that enums are internally considered classes and that > `get_declared_classes()` will retrieve them [1], but the same could be > said about interfaces and traits, yet they do have their own > `get_declared_*()` function. > > Should a `get_declared_enums()` function be added ? > And should the `get_declared_classes()` function be adjusted to exclude enums ? > > I did check the enum RFC [3], but I couldn't find any mention or > discussion about this in the RFC. > > Smile, > Juliette > > > 1: https://3v4l.org/0ub6I > 2: https://www.php.net/manual/en/ref.classobj.php > 3: https://wiki.php.net/rfc/enumerations
Conext: I can't remember the last time I used get_declared_classes() (thanks to autoloading and class_exists() it's a kinda pointless function), 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. I think I'd be open to adding it; my concern would be the overlap with get_declared_classes(), which as you note currently would include enums, and changing that is a BC break (even if a tiny one that I doubt would impact anyone). --Larry Garfield

Unnamed Person

2 years ago
On 15-8-2024 15:09, Larry Garfield wrote:
> On Wed, Aug 14, 2024, at 8:51 PM, Juliette Reinders Folmer wrote: >> L.S., >> >> I just noticed the following, which struck me as weird/inconsistent: >> >> There are four different OO structures in PHP: >> 1. Classes >> 2. Interfaces >> 3. Traits >> 4. Enums >> >> For all four, an `*_exists()` function is available, i.e. >> `class_exists()`, `interface_exists()`, `trait_exists()` and >> `enum_exists()` [2]. >> >> 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()` [2]. >> >> I'm aware that enums are internally considered classes and that >> `get_declared_classes()` will retrieve them [1], but the same could be >> said about interfaces and traits, yet they do have their own >> `get_declared_*()` function. >> >> Should a `get_declared_enums()` function be added ? >> And should the `get_declared_classes()` function be adjusted to exclude enums ? >> >> I did check the enum RFC [3], but I couldn't find any mention or >> discussion about this in the RFC. >> >> Smile, >> Juliette >> >> >> 1: https://3v4l.org/0ub6I >> 2: https://www.php.net/manual/en/ref.classobj.php >> 3: https://wiki.php.net/rfc/enumerations > Conext: I can't remember the last time I used get_declared_classes() (thanks to autoloading and class_exists() it's a kinda pointless function), 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. > > I think I'd be open to adding it; my concern would be the overlap with get_declared_classes(), which as you note currently would include enums, and changing that is a BC break (even if a tiny one that I doubt would impact anyone). > > --Larry Garfield >
Thanks for the response. It was exactly a (custom) autoloader situation which caused me to start wondering about this.

Mike Schinkel

2 years ago
> On Wed, Aug 14, 2024, at 8:51 PM, Juliette Reinders Folmer wrote: > Should a `get_declared_enums()` function be added ?
My answer is "very much, yes."
> On Aug 15, 2024, at 9:09 AM, Larry Garfield <larry@garfieldtech.com> wrote: > Context: I can't remember the last time I used get_declared_classes() (...it's a kinda pointless function)
Your experience is one data point. Another data point is my experience that it is fortunate that `get_declared_classes()` exists as I find it to be a very a useful function. The core library I use for most projects depends on it for class initialization. Your comment made me curious to see if others use it, and there are 8.1k uses on GitHub: - https://github.com/search?q=language%3Aphp+get_declared_classes&type=code Here is how I have used, in a greatly simplified example: function autoload($class) { $classCount = count(get_declared_classes()); if (!file_exists("src/{$class}.php")) { return; } include_once "src/{$class}.php"; foreach (get_declared_classes() as $class) { if (!method_exists($class,'init')) { continue; } $class::init(); } } As an aside, for this use-case I have always wanted PHP to add both 1.) a `get_declared_class_count()` function and 2) optional parameters for `get_declared_classes()` that would allow requesting a subset of classes, such as just the newly added classes. How important this is depends on if PHP maintains that array internally and just returns a reference, or if it instantiates memory and populates the array of classes to return every time the function is called.
> , 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. > > I think I'd be open to adding it; my concern would be the overlap with get_declared_classes(), which as you note currently would include enums, and changing that is a BC break (even if a tiny one that I doubt would impact anyone).
Add another parameter — or include as part of the args array — something like this (though I am sure this could be bikeshed to be better): get_declared_classes(DeclaredClasses:ONLY_CLASSES, ['starting_from'=>174]); get_declared_classes(DeclaredClasses:ALL, ['recently_declared'=>3]); Or even: DeclaredSymbols::get_classes([$args]) <-- returns only classes, not enums DeclaredSymbols::get_enums([$args]) DeclaredSymbols::get_interfaces([$args]) DeclaredSymbols::get_traits([$args]) #fwiw
> On Aug 16, 2024, at 12:23 AM, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote: > Thanks for the response. It was exactly a (custom) autoloader situation which caused me to start wondering about this.
Seems Juliette's use-case may be at least somewhat similar to mine. -Mike

Alexandru Pătrănescu

2 years ago
Hey Juliette,
> Should a `get_declared_enums()` function be added ? >
Yes And should the `get_declared_classes()` function be adjusted to exclude
> enums ? >
No You listed these entities: classes, interfaces, traits and enums. But while there is a clear differentiation between what is considered a class, an interface or a trait, enums are just a subtype of classes. "all enums are classes, but not all classes are enums." So for keeping this valid, `get_declared_classes()` should return all classes (including enums). While `get_declared_enums()` should return all enums. If one really wants all classes except enums, they could use `array_diff( get_declared_classes(), get_declared_enums()`. This would avoid the backward compatibility issues that Larry also mentioned. Regards, Alex

Christoph Becker

2 years ago
On 15.08.2024 at 03:51, Juliette Reinders Folmer wrote:
> Should a `get_declared_enums()` function be added ?
Here we go: function get_declared_enums() { $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; } Porting this to C is left as an excercise for the reader. ;) Hint: <https://github.com/php/php-src/blob/8853cf3ae950a1658054f286117bc8f77f724f00/Zend/zend_builtin_functions.c#L1371-L1399> Note that the terminating folding marker is backwards.
> And should the `get_declared_classes()` function be adjusted to exclude > enums ?
For reasons that have been stated elsewhere in this thread, I don't think so. Cheers, Christoph

Ayesh - PHP.Watch

2 years ago
> Porting this to C is left as an excercise for the reader. ;) Hint: > <https://github.com/php/php-src/blob/8853cf3ae950a1658054f286117bc8f77f724f00/Zend/zend_builtin_functions.c#L1371-L1399> >
I went ahead and created PR https://github.com/php/php-src/pull/15443 along with tests, UPGRADE notice, etc. I think having a `get_declared_enums` function will be helpful. The implementation is simple and straightforward too.

Christoph Becker

2 years ago
On 16.08.2024 at 17:01, Ayesh Karunaratne wrote:
>> Porting this to C is left as an excercise for the reader. ;) Hint: >> <https://github.com/php/php-src/blob/8853cf3ae950a1658054f286117bc8f77f724f00/Zend/zend_builtin_functions.c#L1371-L1399> > > I went ahead and created PR https://github.com/php/php-src/pull/15443 > along with tests, UPGRADE notice, etc.
Thank you very much!
> I think having a `get_declared_enums` function will be helpful. The > implementation is simple and straightforward too.
I concur. Christoph

Unnamed Person

2 years ago
On 16-8-2024 17:01, Ayesh Karunaratne wrote:
> I went ahead and created PR https://github.com/php/php-src/pull/15443 > along with tests, UPGRADE notice, etc. I think having a > `get_declared_enums` function will be helpful. The implementation is > simple and straightforward too.
Thanks for creating the PR Ayesh! I'm presuming it's too late for PHP 8.4, what with feature freeze having come & gone this week. Based on the mostly supportive responses on the list, I wonder whether an RFC is needed. If so, I'd be happy to create an initial draft (for PHP 8.5). Smile, Juliette

Ayesh - PHP.Watch

2 years ago
> > I'm presuming it's too late for PHP 8.4, what with feature freeze having come & gone this week. > > Based on the mostly supportive responses on the list, I wonder whether an RFC is needed. If so, I'd be happy to create an initial draft (for PHP 8.5). >
Thank you! We now have the Release Cycle Update RFC approved (https://wiki.php.net/rfc/release_cycle_update#allow_features_that_do_not_require_an_rfc_in_the_beta_period), and it does allow minor new features during the beta period. Quoting from the RFC:
> [snip] > It therefore seems illogical to allow RFC-requiring features to be merged during the beta period, but not minor improvements that do not require an RFC, such as adding constant, parameters, config options, or even small functions or methods. > > We propose to explicitly allow minor features, optimizations, and internal API / ABI changes, during the beta period.
I think the `get_declared_enums` is small enough to be considered a minor change given we don't change any engine internals. Given the positive response on the mailing list, I think we can have this in PHP 8.4 as well, subject to RMs approval of course. There's also a PR from yesterday (https://github.com/php/php-src/pull/15446) that added a new Curl constant.

Unnamed Person

2 years ago
On 17-8-2024 6:26, Ayesh Karunaratne wrote:
> I think the `get_declared_enums` is small enough to be considered a > minor change given we don't change any engine internals. Given the > positive response on the mailing list, I think we can have this in PHP > 8.4 as well, subject to RMs approval of course.
I'll keep my fingers crossed ;-)

Larry Garfield

2 years ago
On Fri, Aug 16, 2024, at 7:53 PM, Juliette Reinders Folmer wrote:
> On 16-8-2024 17:01, Ayesh Karunaratne wrote: >> I went ahead and created PR https://github.com/php/php-src/pull/15443 along with tests, UPGRADE notice, etc. I think having a `get_declared_enums` function will be helpful. The implementation is simple and straightforward too. > Thanks for creating the PR Ayesh! > > I'm presuming it's too late for PHP 8.4, what with feature freeze > having come & gone this week. > > Based on the mostly supportive responses on the list, I wonder whether > an RFC is needed. If so, I'd be happy to create an initial draft (for > PHP 8.5). > > Smile, > Juliette
I would prefer to have an RFC for this, even if it's short. I'm not against it, I just think there's enough non-trivial questions (eg, impact on get_declared_classes()) that it warrants an RFC process/discussion. --Larry Garfield

Claude Pache

2 years ago
> Le 17 août 2024 à 15:17, Larry Garfield <larry@garfieldtech.com> a écrit : > > On Fri, Aug 16, 2024, at 7:53 PM, Juliette Reinders Folmer wrote: >> On 16-8-2024 17:01, Ayesh Karunaratne wrote: >>> I went ahead and created PR https://github.com/php/php-src/pull/15443 along with tests, UPGRADE notice, etc. I think having a `get_declared_enums` function will be helpful. The implementation is simple and straightforward too. >> Thanks for creating the PR Ayesh! >> >> I'm presuming it's too late for PHP 8.4, what with feature freeze >> having come & gone this week. >> >> Based on the mostly supportive responses on the list, I wonder whether >> an RFC is needed. If so, I'd be happy to create an initial draft (for >> PHP 8.5). >> >> Smile, >> Juliette > > I would prefer to have an RFC for this, even if it's short. I'm not against it, I just think there's enough non-trivial questions (eg, impact on get_declared_classes()) that it warrants an RFC process/discussion. > > --Larry Garfield
Hi, I concur with Larry, it should not be rushed into PHP 8.4, but carefully considered if it is really the correct function to define. Enums were introduced three years ago, and until three days ago, there has been apparently no complaint of the missing function, and it has been proposed less because of a need than because of an apparent lack of symmetry with other `get_declared_*()` functions. So, there is no urgency. Now, the proposed design for `get_declared_enums()` is problematic, because it will bring (or sustain) confusion. An enum is (imo rightfully) considered as a class for the purpose of `class_exists(...)`; and following the same logic, the proposed `get_declared_enums()` returns (rightfully) a subset of the names returned by `get_declared_classes()`. On the other hand, in the mind of many people – as it can be seen in several messages in this thread, and citing the original poster –, there are four different OO structures in PHP: classes, interfaces, traits, and enums. For example, one is tempted to write naïvely: ```php function get_declared_symbols(): array { return [ ...get_declared_classes(), ...get_declared_interfaces(), ...get_declared_traits(), ...get_declared_enums() ]; } ``` not realising that enums will appear twice in the returned list. An alternative design that is less confusing could be the following. I am not really proposing it (as I am personally not convinced that `get_declared_classes()` filtered by `enum_exists()` is not sufficient), but I merely give it as an illustrative example of an api thought to avoid or reduce confusion. We could add an optional parameter to the existing `get_declared_classes()` function, specifying what sort of classes they are interested in: ```php 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 { /* ... */ } ``` —Claude

Nicolas Grekas

2 years ago
> Should a `get_declared_enums()` function be added ? > > Here we go: > > function get_declared_enums() { > $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; > } > > Porting this to C is left as an excercise for the reader. ;) Hint: > < > https://github.com/php/php-src/blob/8853cf3ae950a1658054f286117bc8f77f724f00/Zend/zend_builtin_functions.c#L1371-L1399 > > > > Note that the terminating folding marker is backwards. > > > And should the `get_declared_classes()` function be adjusted to exclude > > enums ? > > For reasons that have been stated elsewhere in this thread, I don't > think so. >
And here is a one liner: function get_declared_enums() { return array_filter(get_declared_classes(), 'enum_exists'); }

Christoph Becker

2 years ago
On 16.08.2024 at 17:11, Nicolas Grekas wrote:
>> Should a `get_declared_enums()` function be added ? >> >> Here we go: >> >> function get_declared_enums() { >> $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; >> } > > And here is a one liner: > > function get_declared_enums() { > return array_filter(get_declared_classes(), 'enum_exists'); > }
Nice! Are you suggesting that there is no need for get_declared_enums() in the core, since it can be implemented in userland with a single function call? If so, I still would argue that it makes sense to implement the function in the core for (a) consistency, and (b) performance reasons. Cheers, Christoph

Bilge

2 years ago
On Fri, 16 Aug 2024, 16:46 Christoph M. Becker, <cmbecker69@gmx.de> wrote:
> On 16.08.2024 at 17:11, Nicolas > > > > And here is a one liner: > > > > function get_declared_enums() { > > return array_filter(get_declared_classes(), 'enum_exists'); > > } > > Nice! > > Are you suggesting that there is no need for get_declared_enums() in the > core, since it can be implemented in userland with a single function call? >
I think he just compulsively golfs inefficient PHP code (I have the same affliction). Cheers, Bilge

Nicolas Grekas

2 years ago
On Fri, 16 Aug 2024, 16:46 Christoph M. Becker, <cmbecker69@gmx.de> wrote:
> On 16.08.2024 at 17:11, Nicolas > > > > And here is a one liner: > > > > function get_declared_enums() { > > return array_filter(get_declared_classes(), 'enum_exists'); > > } > > Nice! > > Are you suggesting that there is no need for get_declared_enums() in the > core, since it can be implemented in userland with a single function call? >
> I think he just compulsively golfs inefficient PHP code (I have the same > affliction). >
Possibly :D I shared this as food for thoughts. I'm not convinced this function would be useful personally, but that's not really an argument ;)

Mike Schinkel

2 years ago
To everyone on this thread and thus nobody in specific (hence my top post): Given that get_declared_*() populates a new ZEND_HASH each time the function is called[1] — which for classes can easily be hundreds of strings — would there be any appetite to: 1. Add an optional parameter — e.g. `get_declared_classes($startingFrom)` — that can limit the number of symbols starting with "n" where if n==100 then it would return array_slice($symbols,100), and 2. Add a function — `get_declared_class_count()` — that returns the current number of declared symbols of whichever kind of symbol, so we can get the "n" before calling `include`/`require` to use with get_declared_*() after calling `include`/`require`? Used in a (very simplistic) example: function autoload($class) { $startingFrom = get_declared_class_count(); if (!file_exists("src/{$class}.php")) { return; } include_once "src/{$class}.php"; foreach (get_declared_classes($startingFrom) as $class) { if (!method_exists($class,'init')) { continue; } $class::init(); } } This would cut down on creating hashes with 100+ strings that get immediately thrown away, and also cut down on memory fragmentation/garbage collector churn. -Mike [1] https://github.com/php/php-src/blob/18d41502da0da1bb3928e60c41f1b821974c2c01/Zend/zend_builtin_functions.c#L1371

Unnamed Person

2 years ago
On 17-8-2024 2:00, Mike Schinkel wrote:
> To everyone on this thread and thus nobody in specific (hence my top post): > > Given that get_declared_*() populates a new ZEND_HASH each time the function is called[1] — which for classes can easily be hundreds of strings — would there be any appetite to: > > 1. Add an optional parameter — e.g. `get_declared_classes($startingFrom)` — that can limit the number of symbols starting with "n" where if n==100 then it would return array_slice($symbols,100), and > > 2. Add a function — `get_declared_class_count()` — that returns the current number of declared symbols of whichever kind of symbol, so we can get the "n" before calling `include`/`require` to use with get_declared_*() after calling `include`/`require`? > > > This would cut down on creating hashes with 100+ strings that get immediately thrown away, and also cut down on memory fragmentation/garbage collector churn. > > -Mike > > [1] https://github.com/php/php-src/blob/18d41502da0da1bb3928e60c41f1b821974c2c01/Zend/zend_builtin_functions.c#L1371 >
Mike, While your suggestion does not directly relate to what triggered my mail to the list, I very very much like the idea. It's as if you are reading my mind or rather, reading one of the autoloaders [1] I need to maintain, in which your above outlined proposal should definitely make for more efficiency. Having said that, the same parameter + extra function would then also be needed for `get_declared_interfaces()`, `get_declared_traits()` - and, if it would be added, `get_declared_enums()`. There is one problem I can see with this approach though: since PHP 7.4, the return value of `get_declared_classes()` (and friends) does not guarantee any particular order anymore [2]. So, an `array_slice($symbols, $n)` may not get you the _latest_ classes loaded, so I think this would only work if the order of classes is guaranteed in some way. Smile, Juliette 1: https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/6fbbc1078094d905f0773421f13830744a144d1e/autoload.php#L153-L247 2: https://www.php.net/manual/en/function.get-declared-classes.php#refsect1-function.get-declared-classes-changelog

Mike Schinkel

2 years ago
Hi Juliette,
> On Aug 16, 2024, at 9:25 PM, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote: > > On 17-8-2024 2:00, Mike Schinkel wrote: >> To everyone on this thread and thus nobody in specific (hence my top post): >> >> Given that get_declared_*() populates a new ZEND_HASH each time the function is called[1] — which for classes can easily be hundreds of strings — would there be any appetite to: >> >> 1. Add an optional parameter — e.g. `get_declared_classes($startingFrom)` — that can limit the number of symbols starting with "n" where if n==100 then it would return array_slice($symbols,100), and >> >> 2. Add a function — `get_declared_class_count()` — that returns the current number of declared symbols of whichever kind of symbol, so we can get the "n" before calling `include`/`require` to use with get_declared_*() after calling `include`/`require`? >> >> >> This would cut down on creating hashes with 100+ strings that get immediately thrown away, and also cut down on memory fragmentation/garbage collector churn. >> >> -Mike >> >> [1] >> https://github.com/php/php-src/blob/18d41502da0da1bb3928e60c41f1b821974c2c01/Zend/zend_builtin_functions.c#L1371 > > Mike, > > While your suggestion does not directly relate to what triggered my mail to the list, I very very much like the idea.
Excellent!
> It's as if you are reading my mind or rather, reading one of the autoloaders [1] I need to maintain, in which your above outlined proposal should definitely make for more efficiency.
Thank you for sharing that. Yes, your use-case is very similar to mine.
> Having said that, the same parameter + extra function would then also be needed for `get_declared_interfaces()`, `get_declared_traits()` - and, if it would be added, `get_declared_enums()`.
That was my assumption and intention, but I did not say so explicitly. I should have. Thanks to Ayesh's excellent PR, I was able to recognize that all of those functions in-fact are implemented via one internal function `get_declared_class_impl()` so it only made sense — to me, at least — that modifying it would enable the same functionality for all four (4) userland functions.
> There is one problem I can see with this approach though: since PHP 7.4, the return value of `get_declared_classes()` (and friends) does not guarantee any particular order anymore [2]. > So, an `array_slice($symbols, $n)` may not get you the _latest_ classes loaded, so I think this would only work if the order of classes is guaranteed in some way.
That is a super relevant insight and one I was actually not aware of. Do you happen to know if it was triggered by an explicit RFC to that effect, or if it resulted because of a side-effect of some other implementation change? From my perspective, that is definitely a BC break and one that I am surprised I never noticed before? Do you also know if it is a case of "not guaranteed, but in-fact actual fact it always works that way, or not?" I have not used my lib that needs that functionality to work in a few years, so maybe it is broken now and those who are now maintaining those apps have had to deal with it? Given that you see value in the same use-case I had which was an autoloader, maybe it would be better to achieve the functionality we need in a different, more efficient way? What if instead PHP were to implement an optional 2nd callback parameter to `include()` / `require()` / `include_once()` / `require_once()` to allow us to capture the symbols loaded and their paths? The callback function could return `void` and accept an array of `$symbols` with the following guaranteed minimum structure? $symbols = array( 'classes' => [], 'interfaces' => [], 'traits' => [], 'enums' => [], ); With that you could reimplement all that PHP code you linked — at least for versions of PHP supporting the callback — to look like this: function loadFile($path) { if (strpos(__DIR__, 'phar://') !== 0) { $path = realpath($path); if ($path === false) { return false; } } if (isset(self::$loadedClasses[$path]) === true) { return self::$loadedClasses[$path]; } include($path, function($symbols) use ($path) { foreach ($symbols['classes'] as $className) { self::$loadedClasses[$path] = $className; self::$loadedFiles[$className] = $path; } }); return self::$loadedClasses[$path]; }//end loadFile() This approach would be a less disruptive than my prior suggestion which would have added four (4) new `*_count()` functions and new optional parameters for four (4) existing function vs. this proposal which just adds new optional parameters for four (4) other existing functions. Further, and the signature(s) required for parameters would be much easier to settle on. I checked the code[2] and if I understand it correctly would (only?) require adding a Zend Observer[3] callback to be notified in `zend_bind_class_in_slot()` although I still cannot figure out where that observer would need to be added. Going this approach would also make the userland code in PHPCodeSniffer a lot more efficient than the code you currently must run for every included file simply to determine what symbols were loaded. What do you think if this alternate approach? -Mike [1] https://github.com/php/php-src/blob/18d41502da0da1bb3928e60c41f1b821974c2c01/Zend/zend_builtin_functions.c#L1371 [2] https://github.com/php/php-src/blob/php-8.3.10/Zend/zend_compile.c#L1231-L1269 [3] https://www.linkedin.com/advice/3/what-some-best-practices-common-pitfalls-using-zend

Unnamed Person

2 years ago
Hi Mike,
> >> There is one problem I can see with this approach though: since PHP 7.4, the return value of `get_declared_classes()` (and friends) does not guarantee any particular order anymore [2]. >> So, an `array_slice($symbols, $n)` may not get you the _latest_ classes loaded, so I think this would only work if the order of classes is guaranteed in some way. > That is a super relevant insight and one I was actually not aware of. > > Do you happen to know if it was triggered by an explicit RFC to that effect, or if it resulted because of a side-effect of some other implementation change?
I honestly don't know, but if I look through the PHP 7.4 RFC list, I have a niggling suspicion it may have something to do with the Preloading RFC [1] ? I may very well be wrong though.
> From my perspective, that is definitely a BC break and one that I am surprised I never noticed before? Do you also know if it is a case of "not guaranteed, but in-fact actual fact it always works that way, or not?"
Well, for the autoloader I previously linked to [2], it was a problematic change which we did have to work around, see [3] and [4] for details of the issues we saw (parent class vs child class order reversals, issues when multiple classes were declared in one file). I haven't deep-dived into the order change, other than to validate the reported issues and proposed fixes.
> What if instead PHP were to implement an optional 2nd callback parameter to `include()` / `require()` / `include_once()` / `require_once()` to allow us to capture the symbols loaded and their paths? The callback function could return `void` and accept an array of `$symbols` with the following guaranteed minimum structure? > > $symbols = array( > 'classes' => [], > 'interfaces' => [], > 'traits' => [], > 'enums' => [], > );
Except `include()` and friends aren't function calls, but language constructs/expressions and don't take parameters as such, so I don't see how that would be possible without changing `include()` and friends to function calls (along the lines of what happened for `exit` in PHP 8.4 with non-parenthesized use still possible to mitigate the otherwise huge breaking change), or alternatively, introducing wrapper functions for the language constructs - so no change to the existing functionality, but new functions with a signature along the lines of `include_and_get_symbols( $path ): array` with the return value being the symbols loaded from $path.
> This approach would be a less disruptive than my prior suggestion
Not so sure about that considering the above ;-)
> What do you think if this alternate approach?
This probably needs some more bike shedding ;-) Smile, Juliette 1: https://wiki.php.net/rfc/preload 2: https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/6fbbc1078094d905f0773421f13830744a144d1e/autoload.php#L153-L247 3: https://github.com/squizlabs/PHP_CodeSniffer/pull/3130 4: https://github.com/squizlabs/PHP_CodeSniffer/issues/3145

Mike Schinkel

2 years ago
> On Aug 17, 2024, at 1:36 AM, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote: >> What if instead PHP were to implement an optional 2nd callback parameter to `include()` / `require()` / `include_once()` / `require_once()` to allow us to capture the symbols loaded and their paths? The callback function could return `void` and accept an array of `$symbols` with the following guaranteed minimum structure? >> >> $symbols = array( >> 'classes' => [], >> 'interfaces' => [], >> 'traits' => [], >> 'enums' => [], >> ); >> > Except `include()` and friends aren't function calls, but language constructs/expressions and don't take parameters as such, so I don't see how that would be possible without changing `include()` and friends to function calls (along the lines of what happened for `exit` in PHP 8.4 with non-parenthesized use still possible to mitigate the otherwise huge breaking change),
I know that those functions can be called as a function and return a value like the following: $return_value = include($path); I was making an assumption that since they be called like a function they could also accept optional parameters, although I admittedly was only assuming and do not know for sure. Do you know for a fact that they cannot take a 2nd parameter, or just assuming like I was? Does anyone else know for certain it is would be possible for `include()` et.al. to accept an optional 2nd parameter? However, even if a 2nd parameter is not a viable option then this could be addressed by instead adding a single new function with a signature of `spl_autoload_callback($fn callback):callback` where the callback has a signature of `function ($symbols array)` which could be used like so: function loadFile($path) { if (strpos(__DIR__, 'phar://') !== 0) { $path = realpath($path); if ($path === false) { return false; } } if (isset(self::$loadedClasses[$path]) === true) { return self::$loadedClasses[$path]; } $prior_callback = spl_autoload_callback(function($symbols) use ($path) { foreach ($symbols['classes'] as $className) { self::$loadedClasses[$path] = $className; self::$loadedFiles[$className] = $path; } }); include($path); spl_autoload_callback($prior_callback); return self::$loadedClasses[$path]; }
>> This approach would be a less disruptive than my prior suggestion > Not so sure about that considering the above ;-)
Well then I think a single new function would be very low on the disruption scale, no?
> This probably needs some more bike shedding ;-)
Shedding of bike undertaken. :-) -Mike

Rowan Tommins [IMSoP]

2 years ago
On 18 August 2024 05:46:09 BST, Mike Schinkel <mike@newclarity.net> wrote:
>I know that those functions can be called as a function and return a value like the following: > >$return_value = include($path);
You are right that it has a return value, but wrong to put its argument in parentheses. This will *not* do what you expect: $success = include($path) && somethingElse(); Because include is not actually a function, what is evaluated is `$path && somethingElse()`, then either `include true` or `include false` (which will of course fail). You instead need to write this: $success = (include $path) && somethingElse(); I thought I'd added this example to the manual, but now can't find it. Consequently, we can't just define optional parameters like we would a normal function. We *could* extend the syntax to allow "include $path, $whatever;" but it probably wouldn't feel very natural. It would be a syntax error to write "include($path, $whatever)" just as "echo $foo, $bar" is valid, but "echo($foo, $bar)" is not. Regards, Rowan Tommins [IMSoP]

Mike Schinkel

2 years ago
> On Aug 18, 2024, at 4:59 AM, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote: > > On 18 August 2024 05:46:09 BST, Mike Schinkel <mike@newclarity.net> wrote: >> I know that those functions can be called as a function and return a value like the following: >> >> $return_value = include($path); > > You are right that it has a return value, but wrong to put its argument in parentheses. This will *not* do what you expect: > > $success = include($path) && somethingElse(); > > Because include is not actually a function, what is evaluated is `$path && somethingElse()`, then either `include true` or `include false` (which will of course fail). > > You instead need to write this: > > $success = (include $path) && somethingElse(); > > I thought I'd added this example to the manual, but now can't find it. > > Consequently, we can't just define optional parameters like we would a normal function. We *could* extend the syntax to allow "include $path, $whatever;" but it probably wouldn't feel very natural. It would be a syntax error to write "include($path, $whatever)" just as "echo $foo, $bar" is valid, but "echo($foo, $bar)" is not.
Hi Rowan, Thank you for confirming the nature of include, et. al. So, if the functionality to be notified via a callback for all symbols loaded is a desired feature by other than me and maybe Juliette then the best approach I've come up with is to add an `spl_autoload_callback()` function that can both get and set the callback to be called after include or its 3 other friends are called. Anyone (else?) disagree? -Mike

A.L.E.C

2 years ago
On 15.08.2024 03:51, Juliette Reinders Folmer wrote:
> Should a `get_declared_enums()` function be added ?
Yes
> And should the `get_declared_classes()` function be adjusted to exclude > enums ?
and Yes. I'd like to see an RFC with these two questions.
-- Aleksander Machniak Kolab Groupware Developer [https://kolab.org] Roundcube Webmail Developer [https://roundcube.net] ---------------------------------------------------- PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

Unnamed Person

2 years ago
On 15-8-2024 3:51, Juliette Reinders Folmer wrote:
> L.S., > > I just noticed the following, which struck me as weird/inconsistent: > > There are four different OO structures in PHP: > 1. Classes > 2. Interfaces > 3. Traits > 4. Enums > > For all four, an `*_exists()` function is available, i.e. > `class_exists()`, `interface_exists()`, `trait_exists()` and > `enum_exists()` [2]. > > 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()` [2]. > > I'm aware that enums are internally considered classes and that > `get_declared_classes()` will retrieve them [1], but the same could be > said about interfaces and traits, yet they do have their own > `get_declared_*()` function. > > Should a `get_declared_enums()` function be added ? > And should the `get_declared_classes()` function be adjusted to > exclude enums ? > > I did check the enum RFC [3], but I couldn't find any mention or > discussion about this in the RFC. > > Smile, > Juliette > > > 1: https://3v4l.org/0ub6I > 2: https://www.php.net/manual/en/ref.classobj.php > 3: https://wiki.php.net/rfc/enumerations >
Not responding to anyone in particular, but just letting you all know that Ayesh and me will work up an RFC for this. I will email the list when we've published it to open the official discussion period. Smile, Juliette