Laravel forms: casting an input before validation

If you’re familiar with casts in Laravel, you’ll know them as a great way to transform values on their way to and from the database. A boolean cast – for example – is stored in the database as a 1 or a 0, whereas inside your code, it will be true or false.

But what about values coming into your application from the user? How can we process those values before they are validated?

Method 1: Inside the Controller

This is the simplest solution, so we won’t go into much detail here. You can simply merge a new value over the existing one:

1public function store(Request $request)
2{
3 $request->merge([
4 'some_value' => $newValue
5 ]);
6}

Method 2: Prepare for Validation

The second way requires the use of a Form Request. This is an a more Laravel way to achieve our aim since we make use of the built-in prepareForValidation method on the form request class.

1protected function prepareForValidation(): void
2{
3 $this->merge([
4 'some_value' => $newValue,
5 ]);
6}

Method 3: Using a Rule class

This is probably the most useful way since it is also the most reusable way and therefore keeps us DRY. Because of this, we’ll go into a lot more detail and give a real-world use case.
Say you want to handle all prices inside your app with the Brick/Money PHP package. This means that on your frontend, the field will be a JS object comprising a currency property (i.e. GBP) and an amount property (e.g. 400 for £4). Inside your app, you want this to be cast to a Money instance.
We can both validate this incoming JS object and cast it to a Money instance by whipping up our own custom validation rule. If you haven’t done one before, you can create a template via php artisan make:rule. We’ll call it Currency.

1<?php
2
3namespace App\Rules;
4
5use Closure;
6use Illuminate\Contracts\Validation\ValidationRule;
7
8class Currency implements ValidationRule
9{
10 public function validate(string $attribute, mixed $value, Closure $fail): void
11 {
12 // Validation logic goes here
13 }
14}

That’s the bare bones of the rule, but we need to add something to it to enable us to interact with the underlying validator. You can find more info on this under the “Accessing additional data” subheading at Validation: Using Rule Objects. Following the instructions here will give us the following:

1<?php
2
3namespace App\Rules;
4
5use Closure;
6use Illuminate\Validation\Validator;
7use Illuminate\Contracts\Validation\ValidationRule;
8use Illuminate\Contracts\Validation\ValidatorAwareRule;
9
10class Currency implements ValidationRule, ValidatorAwareRule
11{
12 protected Validator $validator;
13
14 public function validate(string $attribute, mixed $value, Closure $fail): void
15 {
16 // Validation logic goes here
17 }
18
19 /**
20 * Set the current validator.
21 */
22 public function setValidator(Validator $validator): static
23 {
24 $this->validator = $validator;
25
26 return $this;
27 }
28}

That’s the boilerplate sorted, now we can get to our actual code which will be going in the validate function. The primary aim of this function is to check if the provided value is valid, and if not, call the $fail closure.

As a side effect, we’re also going to mutate the value before it’s validated.

1use Brick\Money\Money;
2
3// ...
4
5public function validate(string $attribute, mixed $value, Closure $fail): void
6{
7 if ($value instanceof Money === false) {
8 $value = Money::ofMinor($value['amount'], $value['currency']);
9 $this->validator->setValue($attribute, $value);
10 }
11
12 if ($value->getMinorAmount()->toInt() < 0) {
13 $fail('Amount must be not be less than zero');
14 }
15}

On line 9, we use the validator we’ve injected into the rule to manually set a value on our validated data. Bear in mind that this will only affect the validated data object/value. If you fetch the value from the $request again, it will still be as it was initially.

Conclusion

The main advantage of the third method is that everywhere we use our Currency rule, the inbound field will automatically be converted to a Money object. If we combine that with a cast on the model, we get an easy way to deal with currency values inside of our application while they are seamlessly transformed for both storage and delivery to the frontend.