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
prepareForValidationmethod which runs before any validation. This method attempts to coerce theplanparameter on the request to an instance ofSubscriptionPlan, if it can't, it'll return the original value. This is so that other rules, such asrequiredcan also respond. - Because of the above, by the time the validation rules are processed we're expecting the
planattribute to be an instance ofSubscriptionPlan. Therefore we've changed the validation rule fromEnumValuetoEnum.
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?