26.5 C
Pakistan
Wednesday, May 29, 2024

Adding attributes to PHP 8.1 enums

PHP 8.1 introduced native support for enums to the language. Enums are a readable, efficient, and type-safe way to condense a small range of possible values that a field in your data model may have. If you need to add to the list later, you have more flexibility when using classes rather than database enums.

Consider a scenario where you have a user data model and that user has access to a specific list of roles.

namespace App\Enums;
 
enum UserRole: string
{
    case Admin = 'admin';
    case TeamAdmin = 'team_admin';
    case Support = 'support';
    case Basic = 'basic';
}

Laravel also supports enums in your data model by casting them to the value you specify in your casts array.

/**
 * The attributes that should be cast.
 *
 * @var array<string,string|class-string>
 */
protected $casts = [
    'role' => UserRole::class,
];

If we try to save a role to our user model that isn’t defined in the enum, an exception will be thrown, thanks to the addition of enum casting.

Using enums to create values for a dropdown in your HTML is a very useful application.

<select name=”roles”>
    @foreach(UserRole::cases() as $role)
        <option value="{{ $role->value }}">{{ $role->name }}</option>
    @endforeach
</select>

Up until you notice the names of each option in the dropdown menu that are visible, everything about the example above appears to be in order. Although Admin and TeamAdmin are excellent variable names, Administrator and Team Administrator should be displayed in the user interface (UI) to make it very obvious to the person in charge of user role management what each role entails.

Even though enums work well for straightforward name/value pairs, you have to get inventive in situations like this where you need to add a third property.

Put PHP attributes here. It is a technique to associate metadata with properties, methods, and classes, which sounds exactly like what we need. It is derived from the idea of annotations in other languages.

We must construct a Description attribute first.

namespace App\Enums\Attributes;
 
use Attribute;
 
#[Attribute]
class Description
{
    public function __construct(
            public string $description,
    ) {
    }
}

We can now define the role names we want that are user-friendly by utilizing the Description attribute in our enum.

namespace App\Enums;
 
enum UserRole: string
{
    #[Description('Administrator')]
    case Admin = 'admin';
 
    #[Description('Team Administrator')]
    case TeamAdmin = 'team_admin';
 
    case Support = 'support';
    case basic = 'basic';
}

We now need to retrieve these attributes, and reflection is the only way to accomplish this. To make this process easier, we should create a trait since we might want to use this attribute on other enums.

namespace App\Enums\Concerns;
 
use Illuminate\Support\Str;
use ReflectionClassConstant;
use App\Enums\Attributes\Description;
 
trait GetsAttributes
{
    /**
     * @param self $enum
     */
    private static function getDescription(self $enum): string
    {
        $ref = new ReflectionClassConstant(self::class, $enum->name);
        $classAttributes = $ref->getAttributes(Description::class);
 
        if (count($classAttributes) === 0) {
                return Str::headline($enum->value);
        }
 
        return $classAttributes[0]->newInstance()->description;
    }
}

If we dissect this method, the first two lines retrieve the enum’s attributes via reflection. We’ve set up a fallback to transform the value (or name) of the enum as our description since not all enums have a Description attribute.

Finally, we extract the description’s value from the enumerated attributes. To deal with this, we can add an additional technique to our trait.

/**
 * @return array<string,string>
 */
public static function asSelectArray(): array
{
    /** @var array<string,string> $values */
    $values = collect(self::cases())
        ->map(function ($enum) {
            return [
                'name' => self::getDescription($enum),
                'value' => $enum->value,
            ];
        })->toArray();
 
    return $values;
}

We can now easily modify the method we call on the enum class in our HTML.

<select name=”roles”>
       @foreach(UserRoles::asSelectArray() as $role)
            <option value=”{{ $role->value }}”>{{ $role->name }}</option>
       @endforeach
</select>

Enums and attributes are relatively new to PHP, but they are a great addition to the language, supporting many common use cases natively.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles