Hidden Methods Advanced Since Laravel 9.x

Rule::forEach()

Apply dynamic validation rules to each element in a nested array — with access to the item's value and fully-expanded attribute name.

Overview

When validating arrays of data, you sometimes need rules that depend on each item's value. Rule::forEach() lets you return a dynamic set of rules for every element in a nested array, with full access to each item's value and attribute path.

Usage

use App\Rules\HasPermission;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpdateCompaniesRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'companies' => ['required', 'array'],
            'companies.*.id' => Rule::forEach(function (
                string|null $value,
                string $attribute,
            ) {
                return [
                    Rule::exists(Company::class, 'id'),
                    new HasPermission('manage-company', $value),
                ];
            }),
        ];
    }
}

The closure receives two arguments:

  • $value — the current element's value (e.g., the company ID)
  • $attribute — the fully-expanded attribute name (e.g., companies.0.id)

Real-World Example

Validate that each line item references a product the user can actually purchase, with quantity not exceeding that product's stock:

use App\Models\Product;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'items' => ['required', 'array', 'min:1'],

            'items.*.product_id' => Rule::forEach(function ($value) {
                return [
                    'required',
                    Rule::exists('products', 'id')->where('is_active', true),
                ];
            }),

            'items.*.quantity' => Rule::forEach(function ($value, $attribute) {
                $index = explode('.', $attribute)[1];
                $productId = request("items.{$index}.product_id");
                $maxStock = Product::find($productId)?->stock ?? 0;

                return [
                    'required',
                    'integer',
                    'min:1',
                    "max:{$maxStock}",
                ];
            }),
        ];
    }
}

When to Use

  • Validating arrays where each item needs rules based on its own value or sibling fields
  • Permission checks per item in a batch operation
  • Dynamic max, exists, or unique rules that vary per array element
  • Any companies.* or items.* pattern where static wildcard rules aren't enough