Laravel - Исключающая валидация полей в Request

Есть запрос такой структуры:

{
    "anything": false,
    "specifications": [
        {
            "nomenclature_id": null,
            "nomenclature_name": "Деревянный камень",
            "anything": true,
        },
        {
            "nomenclature_id": 3, // !!!
            "nomenclature_name": null,
            "anything": true,
        },
    ]
}

Нужно описать правила совместной валидации полей

specifications.*.nomenclature_id
specifications.*.nomenclature_name

таким образом, чтобы одно из них обязательно было null, а другое !== null, при этом:

если nomenclature_id !== null, то проверка на exists:nomenclatures,id

если nomenclature_name !== null, то проверка на string

Пробовал разные варианты с использованием правил типа required_if, exclude_if, sometimes etc. но нужного результата не добился. Понятно, что я могу не мудрить и убрать эту проверку в контроллер, описав её кодом, но ИМХО если что-то можно сделать встроенными средствами фреймворка, то нужно это делать средствами фреймворка.

PHP 8.2

Laravel 10


Ответы (3 шт):

Автор решения: mydls1

Я решил вашу проблему. Я по своему реализовал получение данных в вашем формате ( немного садамаза ) Но это я для наглядности.

  1. не важно что у меня там в view, ,будем считать что массив specifications пришел правильный как у вас.

     public function form(Request $request)
         {
             $request['specifications'] = [
                 [
                     "nomenclature_id" => null,
                     "nomenclature_name" => "Деревянный камень",
                     "anything" => true,
                 ],
                 [
                     "nomenclature_id" => 3,
                     "nomenclature_name" => null,
                     "anything" => true,
                 ]
             ];
    
             $request->validate([
                'specifications.*' => [new TestNull()]
             ]);
    
             dd('validated success');
         }
    
    1. Создаю Валидатор php artisan make:rule TestNull

Условия я сделал, так насколько я понял. (лучше поправить)

class TestNull implements ValidationRule
    {
        /**
         * Run the validation rule.
         *
         * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
         */
        public function validate(string $attribute, mixed $value, Closure $fail): void
        {
            if(!is_null($value['nomenclature_id']) && is_null($value['nomenclature_name'])) {
                $rules = ['nomenclature_id' => 'exists:posts,id'];
                Validator::make($value, $rules)->passes();
            }elseif (is_null($value['nomenclature_id']) && !is_null($value['nomenclature_name'])){
                $rules = ['nomenclature_name' => 'string'];
                Validator::make($value, $rules)->passes();
            }
            else {
                $fail('The problems');
            }
        }
    }
→ Ссылка
Автор решения: vhar

Используйте:

  • nullable для полей, которые могут быть null;
  • required_if для полей, которые должны быть обязательными, если другое поле имеет определенное значение.
  • prohibited_unless для полей, которые должны быть пустыми, если другое имеет значение отличое от указанного.
public function rules(): array
{
    return [
            'specifications.*.nomenclature_id' => 'nullable|required_if:specifications.*.nomenclature_name,null|prohibited_unless:specifications.*.nomenclature_name,null|exists:telegraph_bots,id',
            'specifications.*.nomenclature_name' => 'nullable|required_if:specifications.*.nomenclature_id,null|prohibited_unless:specifications.*.nomenclature_id,null|string'
    ];
}

В вашем случае на примере поля specifications.*.nomenclature_id

required_if:specifications.*.nomenclature_name,null|prohibited_unless:specifications.*.nomenclature_name,null

Поле обязательное если поле specifications.*.nomenclature_name отсутствует или пустое
Поле должно быть null или отсутствовать если поле specifications.*.nomenclature_name не null

→ Ссылка
Автор решения: Капитан Флинт

Из озвученных вариантов я собрал рабочую компиляцию. Сперва создаём кастомное правило валидации:

php artisan make:rule Specifications/IdXorName

Rules/Specifications/IdXorName.php

<?php

// ...

class IdXorName implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail) : void
    {
        if(isset($value['nomenclature_id'], $value['nomenclature_name'])
                || (!isset($value['nomenclature_id']) && !isset($value['nomenclature_name']))) {
            $fail('The ' . $attribute . ' must contain either a nomenclature_id or an nomenclature_name');
        }
    }
}

Http/Requests/SpecificationRequest.php:

<?php

// ...
use App\Rules\Specifications\IdXorName;

class SpecificationOfferRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            // ...
            'specifications.*' => new IdXorName,
            'specifications.*. nomenclature_id' => 'nullable|exists: nomenclatures,id',
            'specifications.*. nomenclature_name' => 'nullable|string|max:255',
            // ...
        ];
    }
}
→ Ссылка