wawa
wawa

Reputation: 5066

Create Enum from Name in PHP

In PHP 8.1, BackedEnum offer a from and tryFrom method to get an enum from a value. How can the same be achieved by non backed enums?

Example BackedEnum:

enum MainType: string
{
    case Full = 'a';
    case Major = 'b';
    case Minor = 'c';
}

var_dump(MainType::tryFrom('a')); // MainType::Full
var_dump(MainType::tryFrom('d')); // null

However this doesn't exist for regular enums.

How would I retrieve a "normal" Enum by name, like:

enum MainType
{
    case Full;
    case Major;
    case Minor;
}

$name = (MainType::Full)->name
var_dump(name); // (string) Full

One option I've found is to simply add a tryFromName function, accepting a string and looping over all the cases like this:

enum MainType
{
    case Full;
    case Major;
    case Minor;

    public static function tryFromName(string $name): ?static
    {
        foreach (static::cases() as $case) {
            if ($case->name === $name) {
                return $case;
            }
        }

        return null;
    }
}

$name = (MainType::Full)->name
var_dump(name); // (string) Full
var_dump(MainType::tryFromName($name)); // MainType::Full

This works, however it seams counter intuitive to enable a foreach loop going over all possibilities just to create an enum.

Therefore the question is, what is the right way to get an Enum in PHP from the name.

Upvotes: 13

Views: 5976

Answers (3)

Bryan Zarzuela
Bryan Zarzuela

Reputation: 1

I can't comment on Duncanmoo's response since I have no reputation yet.

But in PHP 8.4, you can simplify it to

public static function tryFromName(string $name): ?static
{
  return array_find(self::cases(), fn($case) => $case->name === $name);
}

Upvotes: 0

hejdav
hejdav

Reputation: 1367

You can use Reflection:

trait Enum {

    public static function tryFromName(string $name): ?static
    {
        $reflection = new ReflectionEnum(static::class);

        if (!$reflection->hasCase($name)) {
            return null;
        }

        /** @var static */
        return $reflection->getCase($name)->getValue();
    }

}

enum Foo {
    use Enum;

    case ONE;
    case TWO;
}

var_dump( Foo::tryFromName('TWO')   ); // enum(Foo::TWO)
var_dump( Foo::tryFromName('THREE') ); // null

Works also for Backed Enums.

Upvotes: 11

Duncanmoo
Duncanmoo

Reputation: 4181

I love the use of ReflectionEnum in @hejdav's answer and it works PHP8.1+, and this would be my preference.

Unfortunately it fails Phpstan tests saying Method EnumClass::tryFromName() should return EnumClass|null but returns UnitEnum|null., so I resorted to using the following:

trait EnumFromName
{
    /**
     * To mirror backed enums tryFrom - returns null on failed match.
     */
    public static function tryFromName(string $name): ?static
    {
        foreach (self::cases() as $case) {
            if ($case->name === $name) {
                return $case;
            }
        }

        return null;
    }

    /**
     * To mirror backed enums from - throws ValueError on failed match.
     */
    public static function fromName(string $name): static
    {
        $case = self::tryFromName($name);
        if (! $case) {
            throw new ValueError($name.' is not a valid case for enum '.self::class);
        }

        return $case;
    }
}

Upvotes: 7

Related Questions