Casting request parameters to Enums using Laravel Form Requests
I've written about using the prepareForValidation
method in Form Request classes before. We can use it here to manipulate the request parameters on the request before performing validation.
We're going to be using the Laravel Enum package, if you're not familiar with that you might want to check out the docs first before reading on.
The problem
In our fictional application users are able to subscribe to plans. We already have our Enum for the available subscription plans and it looks like this:
namespace App\Enums;
use BenSampo\Enum\Enum;
final class SubscriptionPlan extends Enum
{
const Free = 'free';
const Pro = 'pro';
const Unlimited = 'unlimited';
}
Let's say we have a request to change the subscription plan for a user. We'll take the plan
parameter from the request and pass it to a setPlan
method on our User
model. Our controller looks like this:
class SubscriptionController extends Controller
{
public function update(UpdateSubscriptionRequest $request)
{
$plan = SubscriptionPlan::getInstance($request->plan);
$request->user()->setPlan($plan);
}
}
We're already making use of a Form Request UpdateSubscriptionRequest
for validation:
class UpdateSubscriptionRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'plan' => ['required', new EnumValue(PlanType::class)],
];
}
}
The setPlan
method on our User
model looks like this:
public function setPlan(SubscriptionPlan $plan)
{
$this->plan = $plan;
}
As you can see we're making use of type hinting to make sure we can only ever pass an instance of SubscriptionPlan
to the setPlan
method. As an aside, this is perhaps my favourite advantage to using enums.
The issue here for me is that our controller is responsible for constructing the SubscriptionPlan
instance which is ultimately passed to our setPlan
method on the User.
If feels like the responsibility should lie with the Form Request...
The solution
We make a few changes to our Form Request:
- We've added a
prepareForValidation
method which runs before any validation. This method attempts to coerce theplan
parameter on the request to an instance ofSubscriptionPlan
, if it can't, it'll return the original value. This is so that other rules, such asrequired
can also respond. - Because of the above, by the time the validation rules are processed we're expecting the
plan
attribute to be an instance ofSubscriptionPlan
. Therefore we've changed the validation rule fromEnumValue
toEnum
.
class UpdateSubscriptionRequest extends FormRequest
{
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'plan' => SubscriptionPlan::coerce($this->plan) ?? $this->plan,
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'plan' => ['required', new Enum(SubscriptionPlan::class)],
];
}
}
Now this allows us to simplify our controller:
class SubscriptionController extends Controller
{
public function update(UpdateSubscriptionRequest $request)
{
$request->user()->setPlan($request->plan);
}
}
Nice 'n' tidy, right?