Skip to content

Enums is not a classes, otherwise class_exists() should be renamed to struct_exists() #18288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
gzhegow1991 opened this issue Apr 9, 2025 · 3 comments

Comments

@gzhegow1991
Copy link

gzhegow1991 commented Apr 9, 2025

Description

The following code:

<?php

enum HelloWorldEnum
{}

var_dump(class_exists(HelloWorldEnum::class));

Resulted in this output:

true

But I expected this output instead:

false

PHP Version

PHP 8.4.5 (cli)

Operating System

https://onlinephp.io/

Really, there's strange world.
Bloggers hype that "FINALLY, ENUMS!!11", but actually

function myFunc(MyBackedStringEnum $value)
{}

does not support strings, so i need to MyBackedStringEnum::tryFrom() that should not throw any exceptions instead of ::from()

but

$var = [];
MyBackedStringEnum::tryFrom($var); // TypeError -> ErrorException // GOOD JOB.

I mean, you made Enums to escape array-like lists and calling isset() to check value, but nowadays you need to call

tryFrom() -> surround it to try/catch -> convert to string before it -> do it for any user input after validation.

BEST IMPLEMENTATION.

You know, this one works faster and looks smaller (combo x2):

$index = [
  'hello' => true,
  'world' => true,
];
var_dump(isset($index[ 'hello' ])); // true

btw it throws error too when try to check array key (if index is not \ArrayAccess)
but you dont forced to try/catch + tryFrom.

only purpose is like "enum typehint", that instead of automatic process - forces colleagues to do things manually and then take responsibility about.

language feature that allow developers to drop off responsibility! what a beauty.
yes, valueobjects that means "valid objects" is a pattern, but wrapping plain strings/integers to the object its like "call 3 functions instead of one isset to be cool"

so, solution for case objects/strings/integers would be:

    /**
     * @template-covariant T of \UnitEnum
     *
     * @param T|null               $result
     * @param T|int|string         $value
     * @param class-string<T>|null $enumClass
     *
     * @return class-string|null
     */
    public function type_enum_case(&$result, $value, string $enumClass = null) : bool
    {
        $result = null;

        $hasEnumClass = false;
        if (null !== $enumClass) {
            if (! is_subclass_of($enumClass, '\UnitEnum')) {
                return false;
            }

            $hasEnumClass = true;
        }

        if (is_object($value)) {
            $status = $hasEnumClass
                ? is_a($value, $enumClass)
                : is_subclass_of($value, '\UnitEnum');

            if ($status) {
                $result = $value;

                return true;
            }
        }

        if (! $hasEnumClass) {
            return false;
        }

        if (! (is_int($value) || is_string($value))) {
            return false;
        }

        $enumCase = null;
        try {
            $enumCase = $enumClass::tryFrom($value);
        }
        catch ( \Throwable $e ) {
        }

        if (null !== $enumCase) {
            $result = $enumCase;

            return true;
        }

        return false;
    }

but for enums itself much bigger code type_struct_enum(&$result, $value, $flags) with flags like this, and this function should never check strings to be part of enum, just return Enum class if enum case object provided, or Enum class name provided.

if (! defined('_PHP_STRUCT_TYPE_CLASS')) define('_PHP_STRUCT_TYPE_CLASS', 1 << 1);
if (! defined('_PHP_STRUCT_TYPE_INTERFACE')) define('_PHP_STRUCT_TYPE_INTERFACE', 1 << 2);
if (! defined('_PHP_STRUCT_TYPE_TRAIT')) define('_PHP_STRUCT_TYPE_TRAIT', 1 << 3);
if (! defined('_PHP_STRUCT_TYPE_ENUM')) define('_PHP_STRUCT_TYPE_ENUM', 1 << 4);
if (! defined('_PHP_STRUCT_TYPE_ALL')) define('_PHP_STRUCT_TYPE_ALL', (1 << 5) - 1);

if (! defined('_PHP_STRUCT_EXISTS_TRUE')) define('_PHP_STRUCT_EXISTS_TRUE', 1 << 5);
if (! defined('_PHP_STRUCT_EXISTS_FALSE')) define('_PHP_STRUCT_EXISTS_FALSE', 1 << 6);
if (! defined('_PHP_STRUCT_EXISTS_IGNORE')) define('_PHP_STRUCT_EXISTS_IGNORE', 1 << 7);

and then check _PHP_STRUCT_EXISTS_IGNORE -> only regex
_PHP_STRUCT_EXISTS_TRUE -> only exists with flags TYPE
_PHP_STRUCT_EXISTS_FALSE -> exists, then regex...

@iluuu1994
Copy link
Member

Enums is not a classes

This is factually incorrect.

https://wiki.php.net/rfc/enumerations

Enumerations are built on top of classes and objects.

We have made the very conscious decision to base enums on classes so that they can share a large amount of the existing architecture, like methods, interfaces, constants for cases, etc. PHP isn't the only language that does this either.

tryFrom() -> surround it to try/catch -> convert to string before it -> do it for any user input after validation.

tryFrom is intended to validate values of the correct type. Some argument could be made for accepting arbitrary values. However, this behavior was specified and voted on, so changing it requires an RFC.

var_dump(isset($index[ 'hello' ])); // true
but you dont forced to try/catch + tryFrom.

This is not true. The fair comparison would be this, which does throw.

BEST IMPLEMENTATION.

Please keep the snarky comments to yourself. Otherwise, I will stop engaging with your issues, as most others have already chosen to.

@nielsdos
Copy link
Member

nielsdos commented Apr 9, 2025

It's worth noting also that the following RFC is a little bit related: https://wiki.php.net/rfc/get_declared_enums

@nielsdos
Copy link
Member

Since there is an RFC for basically this, I'll mark this issue as requiring an RFC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants