Important: The information in this article is over 12 months old, and may be out of date or no longer relevant.
But hey, you're here anyway, so give it a read see if it still applies to you.
I’m currently working on a project in Laravel where I needed to validate the sum of a property in an array adds up to a specific value. For example:
1$data = [ 2 'items' => [ 3 [ 4 'name' => 'Banana', 5 'number' => 24 6 ], 7 [ 8 'name' => 'Rama', 9 'number' => 7610 ]11 ]12];
I needed to make sure the sum of the number
properties added up to 100.
In the past, I wrote a closure in my Form Request, but that shows this is something that is needed more than once, so rather than repeat the logic every time I needed it, why not write a custom rule? Laravel makes it so easy too.
Heads up too: this rule is for Laravel 9.18+ where Invokable validation classes were added.
Invokable rules have a single __invoke
method where the validation logic takes place, and if fails, can call the $fail
closure that is available.
This rule is designed to be attached to the outer array – not the property in the array. The logic for the validation here is to convert the array to a collection, then using the collection’s sum
method, confirm it adds up to the required amount. And if it does not, it fails.
1public function __invoke($attribute, $value, $fail) 2{ 3 $array = collect($value); 4 5 if ($array->sum($this->property) != $this->sum) { 6 $fail(__('The sum of the :property must add up to :sum.', [ 7 'property' => $this->property, 8 'sum' => $this->sum 9 ]));10 }11}
So hang tight… where did $this->property
and $this->sum
come from? Given the rule is applied to the array itself, you need to tell the rule what property to sum:
1new ExactSumRule('number')
Within the Rule’s constructor, the internal property
attribute is being set, so the rule will now look for the sum of the “number” property. And sum
too.
For my initial needs, I required a total of 100 for my use case, but I can also think of times where a different total is needed. The Rule also accepts a second parameter to change the required sum:
1new ExactSumRule('number', 50)
By default, the Rule will sum for 100. But above, it will sum for 50.
This now offers flexibility to check different properties in an array, with different totals, in a reusable Rule.
And passes 7 tests confirming parameters, behaviour and options – I’ve got these written as Pest tests if you’re interested and have read this far: reach out to me if you’re wanting these.
Here’s a look at the entire rule:
1namespace App\Rules; 2 3use Illuminate\Contracts\Validation\InvokableRule; 4use InvalidArgumentException; 5use TypeError; 6 7class ExactSumRule implements InvokableRule 8{ 9 public function __construct(10 protected string $property,11 protected float $sum = 10012 ) {13 if ($this->sum < 0) {14 throw new InvalidArgumentException('The $sum must be a value greater than 0.');15 }16 }17 18 public function __invoke($attribute, $value, $fail): void19 {20 $array = collect($value);21 22 try {23 if ($array->sum($this->property) != $this->sum) {24 $fail(__('The sum of the :property must add up to :sum.', [25 'property' => $this->property,26 'sum' => $this->sum27 ]));28 }29 } catch (TypeError $error) {30 $fail(__('The values for :property could not be added.', [31 'property' => $this->property32 ]));33 }34 }35}
In the __invoke
method, a try
block will catch any TypeError
s thrown, such as when you provide a property that cannot be summed using the collection's sum
method, and will fail the validation.
And here’s how we can use it for the number
property on the items
array:
1// ...2'items' => [3 'required',4 'array',5 'min:1',6 new ExactSumRule('number')7]8// ...
And now to check the number
properties add up to 50:
1// ...2'items' => [3 'required',4 'array',5 'min:1',6 new ExactSumRule('number', 50)7]8// ...
Or even 15.5:
1// ...2'items' => [3 'required',4 'array',5 'min:1',6 new ExactSumRule('number', 15.5)7]8// ...
Happy summing!
You may be interested in...
valetTls
to detectTls
in your vite.config.js for Laravel Herd
December 12th, 2023