Validate BelongsToMany attached keys exists

28 views Asked by At

How to validate foreign keys (as UUID) in attached elements like below:

$user->languages()->attach([
    '9a347a21-b6db-42ae-85a5-24a270946eec' => [ // UUID of language
        'additional_field' => 2,
        'additional_field2' => '',
    ],
    '9a347a21-e0b8-4475-a262-e63042d1a6f6' => [ // UUID of language
        'additional_field' => 321,
        'additional_field2' => 'aaa bb c',
    ],
]);

My relation in User looks like:

public function languages(): BelongsToMany
{
    return $this->belongsToMany(Language::class)
        ->using(UserLanguage::class)
        ->withPivot(['additional_field', 'additional_field2']);
}

I can validate by

  • languages
  • languages.*.additional_field
  • languages.*.additional_field2

But how to validate UUID in above example is exists in Language model?

1

There are 1 answers

3
IGP On

The exists validation should work the same way as it does with regular numeric ids. However, it gets tricky to validate if you pass the ids as array keys.

One way I'd do it is like this

'languages' => ['array'],
'languages.*' => ['array'],
'languages.*.language_uuid' => ['required', 'exists:languages,uuid'],
'languages.*.pivot' => ['array'],
'languages.*.pivot.additional_field' => [...],
'languages.*.pivot.additional_field2' => [...],

The data sent would look like

"languages": [
    [
        "language_uuid": "9a347a21-b6db-42ae-85a5-24a270946eec",
        "pivot": {
            "additional_field": 2,
            "additional_field2": ""
        }
    ],
    [
        "language_uuid": "9a347a21-e0b8-4475-a262-e63042d1a6f6",
        "pivot": {
            "additional_field": 321,
            "additional_field2": "aaa bb c"
        }
    ],
]

And the way you could easily insert stuff in the endpoint with the validated request would be

$validated = $request->validated();
$user = User::create($validated); // The languages key being present here shouldn't be a problem if you've defined which fields are fillable in the User model.

$languages = collect($validated['languages'])
    ->map(fn ($data) => [ $data['language_uuid'] => $data['pivot'] ])
    ->all();
$user->languages()->attach($languages);

Assuming you send the data like this:

"languages": [
    "9a347a21-b6db-42ae-85a5-24a270946eec": {
        "additional_field": 2,
        "additional_field2": ""
    },
    "9a347a21-e0b8-4475-a262-e63042d1a6f6": {
        "additional_field": 321,
        "additional_field2": "aaa bb c"
    }
]

The only way I can think of that would validate the keys without making a new Rule would be to use a Callback, like the validator or the FormRequest's after() method.

Laravel 10.x - Validation - Performing Additional Validation on Form Requests

public function rules(): array
{
    return [
        'languages' => ['array'],
        'languages.*' => ['array'],
        'languages.*.additional_field' => [...],
        'languages.*.additional_field2' => [...],
    ];
}

public function after()
{
    return [
        function (Validator $validator) {
            $uuids = array_keys($validator->getData()['languages']);

            foreach ($uuids as $uuid) {
                if (Language::where('uuid', $uuid)->doesntExist()) {
                    $validator->errors()->add(
                        "languages.{$uuid}",
                        __('validation.exists', ['attribute' => 'language uuid']),
                    );

                    return;
                }
            }
        }.
    ];
}