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:

  1. We've added a prepareForValidation method which runs before any validation. This method attempts to coerce the plan parameter on the request to an instance of SubscriptionPlan, if it can't, it'll return the original value. This is so that other rules, such as required can also respond.
  2. Because of the above, by the time the validation rules are processed we're expecting the plan attribute to be an instance of SubscriptionPlan. Therefore we've changed the validation rule from EnumValue to Enum.
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?