enum class in ternary expression

Viewed 779

Enums were introduced to PHP very recently. I'm trying them out in a laravel project. I've got my enum class here:

namespace App\Enums;

enum AlertType
{
    case SUCCESS;
    case ERROR;
}

I'm trying to create an alert class that will take the enum in the constructor to set the severity of the alert, which will decide what colour it is rendered as to the user. Here is that class:

<?php

namespace App\View\Components;

use App\Enums\AlertType;
use Illuminate\View\Component;

class Alert extends Component
{
    public string $contextClass;

    public function __construct(public string $message, AlertType $alertType = AlertType::SUCCESS)
    {
        $this->setContextClassFromAlertType($alertType);
    }

    public function setContextClassFromAlertType(AlertType $alertType)
    {
        $this->contextClass = ($alertType === AlertType::SUCCESS ? 'success' : 'error');
    }

    public function getClassListFromType()
    {
        return [
            'border-' . $this->contextClass,
            'text-' . $this->contextClass
        ];
    }

    public function render()
    {
        return view('components.alert', [
            'class' => implode(' ', $this->getClassListFromType())
        ]);
    }
}

This alert will be used in a login form which is built using Laravel Livewire and blade components:

<form class="grid grid-cols-min-auto-1 gap-x-2" id="login" action="/login" method="post">
    <x-alert :message="$errorMessage"/>
    @csrf
    <label for="email" class="mb-2 text-lg text-sans w-min mt-2 font-thin">Email</label>
    <x-basic-input wire:model="email" placeholder="{{ $emailPlaceholder }}"/>
    <label for="password" class="mb-2 text-lg text-sans w-min mt-2 font-thin">Password</label>
    <x-basic-input wire:model="password"/>
</form>

When I come to display the login form I am getting the following error:

Cannot instantiate enum App\Enums\AlertType (View: /resources/views/livewire/forms/login.blade.php)

I think there is something wrong with my enum usage in the Alert component, but I'm not sure where. Can anyone point me in the right direction. I've looked at the rfc for enums but I can't see anything obvious that I'm doing wrong

2 Answers

I was able to reproduce this error; in my case the stack trace led back to the barryvdh/laravel-debugbar package, not sure if this is the same for you. I was able to resolve it by changing the enum to a backed enum.

I'd recommend making this change regardless, as I expect in a lot of cases strings will be easier to work with than enum instances. (Though TBH this looks like trying to use a new feature just because it's there, not because it makes sense.)

namespace App\Enums;

enum AlertType: string
{
    case SUCCESS = 'success';
    case ERROR = 'error';
    case INFO = 'info';
}

In a backed enum, each item has a string representation that can be accessed using the value property, which we do in the constructor:

<?php

namespace App\View\Components;

use App\Enums\AlertType;
use Illuminate\View\Component;

class Alert extends Component
{
    public string $contextClass;

    public function __construct(
        public string $message,
        AlertType $alertType = AlertType::SUCCESS,
    )
    {
        $this->contextClass = $alertType->value;
    }

    public function getClassListFromType()
    {
        return [
            'border-' . $this->contextClass,
            'text-' . $this->contextClass
        ];
    }

    public function render()
    {
        return view('components.alert', [
            'class' => implode(' ', $this->getClassListFromType())
        ]);
    }
}

You're now able to use the from() or tryFrom() methods which can add flexibility with alert types saved in a variable. For example:

<x-alert :message="$errorMessage" :type="App\Enums\AlertType::from($errorType)"/>

I think the problem here is due to dependency injection. In the constructor I'm typehinting an enum, so the Laravel container is trying to create an instance of that class to pass in which doesn't work because enums are instantiable.

If I update the container to manually resolve the typehinted instance like this:

$this->app->bind(Alert::class, function ($app) {
    return new Alert('this is a message', AlertType::SUCCESS);
});

Then the issue is resolved and it works as expected. I'll need to change the way I'm using enums here as @miken32 suggests to use a backed enum and rely on the strings instead. Otherwise I'd need to override the container injection for every method where I wanted to pass an enum.

Related