Andrew Willis
Andrew Willis

Reputation: 2349

Laravel / nested validation

I am building a component-driven API for a basic page-builder system and have hit a stumbling block when it comes to validation.

First, I want to explain the use-case.

if we have a component (for example in Vue) in /components/ProfileCard.vue

<script>
export default {
    props: {
        name: String,
        age: Number,
        avatar: String
    }
}
</script>

I am creating a component in the backend components.php config:


<?php

return [
    'profile' => [
        'component' => 'ProfileCard',
        'rules' => [
            'name' => [
                'required',
            ],
            'age' => [
                'required',
                'number',
            ],
            'avatar' => [
                'required',
            ]
        ],
    ],
];

Which checks and validates every time a profile card component is submitted.

Creating a custom validation rule for Component, I am able to say "the ProfileCard component is not valid" but I am not able to merge / nest the validation rules:

Component.php

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;

class Component implements Rule 
{
    protected $validator = null;

    public function passes($attribute, $value)
    {
        $components = config('components');
        $component = $value['component'];

        if (isset($components[$component])) {
            return false;
        }

        $c = $components[$component];
        $this->validator = Validator::make($value['data'], $c['rules'], $c['messages'] ?? '');
        return $this->validator->passes();
    }

    public function message() 
    {
        if (is_null($this->validator)) {
            return 'The component does not exist';
        }
        return $this->validator->errors();
    }
}

Has anybody got any experience doing anything like this or can anybody point me in the right direction towards a solution?

I am ideally looking for a solution which is applicable while using Laravel's FormRequest validation, like so:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Unique;
use App\Rules\Component;

class CreateUserRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'email' => [
                'required',
                'email',
                new Unique('users', 'email'),
            ],
            'profile' => [
                'required',
                new Component(),
            ]
        ];
    }
}

The data would come in like so:

{
    "email": "[email protected]",
    "profile": {
        "component": "ProfileCard",
        "data": {
           "name": "Test",
           "age": 49,
           "avatar": "https://example.com/avatar.png"
        }
    }
}

I have updated the question with the progress I have made myself, you can return a MessageBag in the messages method on the rule, however, this creates a slight problem, the response comes back as follows:


    "message": "The given data was invalid.",
    "errors": {
        "profile": [
            {
                "name": [
                    "The name field is required."
                ],
                "age": [
                    "The age field is required."
                ],
                "avatar": [
                    "The avatar field is required."
                ],
            },
            ":message"
        ]
    }

Clearly this is an improvement but it's still not as usable, we don't have a ':message' and the validation errors are nested in an object in the "profile" array.

Upvotes: 0

Views: 1466

Answers (1)

mrhn
mrhn

Reputation: 18916

Your approach seems like you are over complicating a simple problem. I would never do a validation in a validation rule. Instead do rules that is dependent on the component and adjust it in the form request accordingly. You can easily do nested rules like so.

[
    'profile.name' => 'string',
]

Do the rest of the logic in the form request. The strategy is to have rules based on the request input and your config file, based on what you already tried.

public function rules()
{
    // i do not know how you determine this key
    $componentKey = 'profile';

    $rules = [
        ...,
        $componentKey => [
            'required',
        ]
    ];

    $inputComponent= $this->input('profile')['component'];
    $components = config('components');

    // based on your data this seems wrong, but basically fetch the correct config entry
    $component = $components[$inputComponent];

    foreach ($component['rules'] as $key => $value) {
            $rules[$componentKey  . '.' . $key] => $value;
    }

    return $rules;
}

There is some parts of your code where i can't figure out what your data means, i do not know how you get the component key profile and your code based on the config and the component field seems wrong and should instead do a loop with a where condition. I think that this can get you in the right direction, this solution will solve your message problem and be way simpler.

Upvotes: 1

Related Questions