GubGubGub
GubGubGub

Reputation: 101

Getting an PHP enum from dynamic name

I´m trying to create an php 8.1 enum from a dynamic name. Seems not to be possible. So given an enum

enum Foo {
    case bar;
}

The following works of course: Foo::bar While this doesn´t:

$name = "bar";
Foo::${$name}

This results in: Access to undeclared static property App\Console\Commands\Foo::$bar

I tried various tricks here, to no avail. It seems not to be possible to get an enum instance from a dynamic name. My custom and quick workaround looks like this, a static factory:

/**
 * @throws \Exception
 */
public static function fromName(string $name) : self {

    foreach(Foo::cases() as $enum){
        if($enum->name === $name){
            return $enum;
        }
    }
    throw new \Exception("Not a valid enum name");
}

I could put this in a trait and inherit this in all my enums, yes, problem solved.

My question is: am I missing something here? Is it really not possible to instantiate an enum with native php methods? Or am I thinking in the wrong direction?

The pre php8.1 class-as-enum composer packages used to have those convenience methods, see https://github.com/bensampo/laravel-enum So why is this pretty common case not part of the specification (just curious)?

Upvotes: 10

Views: 13489

Answers (5)

jave.web
jave.web

Reputation: 15032

PHP 8.3+

Following syntax is supported since PHP 8.3:

<?php

$enumName = 'Something';
SomeEnum::{$enumName};
SomeEnum::{$enumName}->value;

This works for both constants and enum cases. It was basically done for constants as "dynamic class constant fetch": https://www.php.net/releases/8.3/en.php#dynamic_class_constant_fetch as since enums are basically implemented as a special case of class with special contants (cases) - it works for them too.

PHP 8.1 & 8.2

You can use constant() as you would use for a constant, but be aware of namespaces! by the way this is the same issue as with defined & const definition - even when inside the namespace, you have to specify it in the reference string!

Namespace example

namespace SomeNamespace;
enum MyEnum: int {
    case SomeName = 42;
}

$name ='SomeName';
echo constant("SomeNamespace\MyEnum::$name")->value;

Simple example (without namespace)

enum MyEnum: int {
    case SomeName = 42;
}

$name ='SomeName';
echo constant("\MyEnum::$name")->value;

Upvotes: 6

albert.garipov
albert.garipov

Reputation: 473

One-liner:

$name = 'bar';
$enum = array_column(Foo::cases(), null, 'name')[$name] ?? null;

Upvotes: 1

Eslam Saeed
Eslam Saeed

Reputation: 416

The most simple way for that is using the constant() function:

<?php namespace App\Enums;


enum FooEnum: int
{
    case FOO = 123;
    case BAR = 456;
   
    public static function fromName(string $name){
        
        return constant("self::$name");
    }
}

Usage:


$name = "FOO";
dump(App\Enums\FooEnum::fromName($name));

/*
return 
App\Enums\FooEnum {
  +name: "FOO"
  +value: 123
}
*/

Upvotes: 17

JoshuaDoshua
JoshuaDoshua

Reputation: 112

Alternatively, you could make your enum backed and use the ::from method.

// note: type-hinting required
enum Status: int
{
    case OK = 200;
    case ERROR = 500;
}

$statusValue = 200;
$status = Status::from($statusValue);

(returns Status::OK)

If you're using Laravel casting, I believe it requires you to use a backed enum anyway. And in your model you just need to add that cast on your model

...
protected $casts = [
    'status' => \App\Emums\Status::class,
];

Upvotes: 4

Kyrre
Kyrre

Reputation: 698

Could you maybe do something like this?


enum Status {
  case OK;
  case DENIED;
}

$name = "OK";
$reflection = new \ReflectionEnum(Status::class);
if ( $reflection->hasConstant( $name ) ) {
  var_dump($reflection->getConstant( $name ));
}

Output: enum(Status::OK)

Upvotes: 2

Related Questions