[PHP-DEV] Should there be a `get_declared_enums()` function ?

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 ext/standard: Add `get_declared_enums` function by Ayesh · Pull Request #15443 · php/php-src · GitHub 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:

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:

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

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 :wink:

Well then I think a single new function would be very low on the disruption scale, no?

This probably needs some more bike shedding :wink:

Shedding of bike undertaken. :slight_smile:

-Mike

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

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]

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

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

···

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