Skip to content

Conversation

@lucasacoutinho
Copy link

When writing custom validation rules for array items, accessing sibling data requires manual path manipulation. Consider an API for bulk order creation:

{
    "orders": [
        {
            "customer_id": 123,
            "items": [
                {
                    "product_id": 1,
                    "quantity": 10,
                    "unit_price": 150.00,
                    "discount": 2000.00
                }
            ]
        }
    ]
}

To validate that discount doesn't exceed the line total (quantity x unit_price), you currently need:

class DiscountMustNotExceedLineTotal implements ValidationRule, DataAwareRule
{
    protected array $data = [];

    public function setData(array $data): static
    {
        $this->data = $data;
        return $this;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        // $attribute = "orders.0.items.1.discount"
        // Need to extract indices and navigate the nested structure
        preg_match('/^orders\.(\d+)\.items\.(\d+)\./', $attribute, $matches);
        $orderIndex = $matches[1] ?? null;
        $itemIndex = $matches[2] ?? null;

        if ($orderIndex === null || $itemIndex === null) {
            return;
        }

        $item = $this->data['orders'][$orderIndex]['items'][$itemIndex] ?? [];
        $lineTotal = ($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0);

        if ($value > $lineTotal) {
            $fail("The discount cannot exceed the line total of {$lineTotal}.");
        }
    }
}

We could make the experience much nicer with a new and more limited ScopeAwareRule interface that automatically receives the current array item's data:

class DiscountMustNotExceedLineTotal implements ValidationRule, ScopeAwareRule
{
    protected array $scope = [];

    public function setScope(array $scope): static
    {
        $this->scope = $scope;
        return $this;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $lineTotal = ($this->scope['quantity'] ?? 0) * ($this->scope['unit_price'] ?? 0);

        if ($value > $lineTotal) {
            $fail("The discount cannot exceed the line total of {$lineTotal}.");
        }
    }
}

@lucasacoutinho lucasacoutinho marked this pull request as draft December 10, 2025 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant