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' => $newValue5 ]);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(): void2{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<?php2
3namespace App\Rules;4
5use Closure;6use Illuminate\Contracts\Validation\ValidationRule;7
8class Currency implements ValidationRule9{10 public function validate(string $attribute, mixed $value, Closure $fail): void11 {12 // Validation logic goes here13 }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<?php2
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, ValidatorAwareRule11{12 protected Validator $validator;13
14 public function validate(string $attribute, mixed $value, Closure $fail): void15 {16 // Validation logic goes here17 }18
19 /**20 * Set the current validator.21 */22 public function setValidator(Validator $validator): static23 {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): void6{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.