illuminatech / validation-composite

Allows uniting of several validation rules into single one for easy re-usage

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Issue with using `required_with` for nested objects

misa-neopix opened this issue · comments

Hi, I have an issue using composite rules.

Namely, I'm accepting an object with two properties in my form request, and I want to have composite rules for each of the properties.

In order to ensure the structure of the object, I want to use required_with in both composite rules, and that's proven to be highly problematic.

Here's what I need:

Form request:

<?php

namespace App\Http\Requests\Api;

use App\Http\Requests\FormRequest;
use App\Rules\CurrencyPriceAmountRule;
use App\Rules\CurrencyPriceCurrencyRule;

class ExpenseRequest extends FormRequest
{
    public function rules()
    {
        return [
            'total_price' => ['nullable', 'array'],
            'total_price.amount' => CurrencyPriceAmountRule::forPrice('total_price'),
            'total_price.currency' => CurrencyPriceCurrencyRule::forPrice('total_price'),
        ];
    }
}

Composite rule for amount:

<?php

namespace App\Rules;

use Illuminatech\Validation\Composite\CompositeRule;

class CurrencyPriceAmountRule extends CompositeRule
{
    private string $priceObject;

    public static function forPrice(string $priceObjectName): self
    {
        $rule = new self();

        $rule->priceObject = $priceObjectName;

        return $rule;
    }

    protected function rules(): array
    {
        return [
            "required_with:{$this->priceObject}",
            'numeric',
        ];
    }
}

Composite rule for currency:

<?php

namespace App\Rules;

use Illuminatech\Validation\Composite\CompositeRule;

class CurrencyPriceCurrencyRule extends CompositeRule
{
    private string $priceObject;

    public static function forPrice(string $priceObjectName): self
    {
        $rule = new self();

        $rule->priceObject = $priceObjectName;

        return $rule;
    }

    protected function rules(): array
    {
        return [
            "required_with:{$this->priceObject}",
            "string",
            "size:3",
        ];
    }
}

And everything's working well when there is valid input, however when there is no total_price field in the input validation fails. If total_price.amount were present without total_price.currency, or vice versa, validation should fail. However, if total_price is completely missing validation should not fail.

I've tracked the issue down to this line:

Arr::set($data, $attribute, $value); // ensure correct validation for array attributes like 'item_ids.*' or 'items.*.id'

What happens is the CompositeRule::passes() method receives total_price.amount as the $attribute argument, and null as the $value argument, and then the Arr::set() invocation creates the following structure for the $data variable:

[
    'total_price' => [
        'amount' => null,
    ],
]

And then validation fails because it detects total_price is present, however total_price.amount is null.

I'd be happy to think about a possible solution and submit a PR, I just want to know that what I'm describing is indeed undesired behaviour and a proper PR would get merged.

@klimov-paul I understand your point, however what I failed to mention in the issue is the usage of \Illuminate\Contracts\Validation\ImplicitRule. This interface can be used for making custom rule classes be validated in the same way as implicit rules (see this and this).

However, even when using the interface the validation still fails due to the issue I explained above.

What I was suggesting is refactoring the package to work with the ImplicitRule, that way the rules mentioned in the readme would be able to be used in composite rules as long as it implements the said interface.

Sorry for forgetting to mention this in the original post.

@klimov-paul Any thoughts on my comment?