Description
Normal Enums don't work with the new validation rule 'Illuminate\Validation\Rules\Enum'.
Example
As an example you have enum like the following definition:
enum Status
{
case OK;
case ERROR;
}
you also have a select HTML field like the following in your form:
<select name='status' >
<option value='OK'>Ok</option>
<option value='ERROR'>Error</option>
</select>
In your controller the request gets validated like that
use Illuminate\Validation\Rules\Enum;
$request->validate([
...
'status' => new Enum(Status::class)
]);
This won't work.
Sadly, the code in Illuminate\Validation\Rules\Enum
validate the value with tryFrom and since its a method only available when using BackedEnum's, it fails and returns false.
A Solution
You could add the following, so you have a BackedEnum, and everything works fine:
enum Status: string
{
case OK = "OK";
case ERROR = "ERROR";
}
Opinion
What I don't like about that approach, is the duplication of the value. There are definitly usecases to go with BackedEnum's, but if I have a list of a couple of enum cases and every one of them has the same value as the name of the case and I misspell one value wrong (e.g. miss a 'R' in 'EROR') the code won't work (but a case like ERROR would still exist). The added string information also doesn't add any value to the code, if it's allways the same name.
The Laravel Doc should be mentioning that the rule only works for BackedEnum's. The rule could also be renamed to BackedEnum, like the interface (php.net/manual/de/class.backedenum.php) that is needed for it to work.
My current solution
For the above problem I did go back to the Rule Validation:
$request->validate([
...
'status' => Rule::in([Status::OK->name, Status::ERROR->name])
]);
But this is a pain in the a**, if you add new cases or rename them. So what i did, since you can't extend an enum, was adding a trait, which adds methods that are equivalent to the methods that backed enums have. Like from and tryFrom and also an equivalent cases method, that is called all, that returns all case names as an array, instead of all cases as enum objects.
My trait looks like the following:
namespace App\Classes;
trait FooBar
{
public static function all()
{
return array_map(function($c)
{
return $c->name;
},self::cases());
}
public static function get(string $name)
{
if(defined("self::$name"))
return constant("self::$name");
throw new ValueError("{$name} is not a valid case in enum '".get_called_class()."'");
}
public static function tryGet(string $name)
{
return defined("self::$name") ? constant("self::$name") : null;
}
}
In all my enums I just use the trait:
use App\Classes\FooBar;
enum Status
{
use FooBar;
case OK;
case ERROR;
}
Now in the validation i go back and change the list of all cases to the following:
$request->validate([
...
'status' => Rule::in(Status::all())
]);
Do you have a different approach? A better approach?