Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

fcode's avatar
Level 7

Unique validation on related model

Hello guys. I need some help with validation.

This is my situation. I have a questions table and an options table. A question can have many options for up to five which means that options have a column for question_id. In the options table is a label field that is used to indicate the option as a, b, c, d, or e. I want to make the labels of the options for a particular question to be unique when creating a new question with options. This means that there cannot be two options with the same label belonging to the same question_id. The options are sent together with the question in the same request as an array of objects.

I don't want to add a unique constraint to the option label on the database level because that will affect the entire table. But the uniqueness is only on the options belonging to the same question.

How do I achieve this using Laravel validation FormRequest? Or any other way to achieve this?

Your suggestions will be highly appreciated.

0 likes
10 replies
fcode's avatar
Level 7

The options is an array of objects and are sent with the question in one request payload. At this point, the question has not been created so there is no way to have access to the question_id. This is a sample of the request payload:

{
    "number": "1",
    "type": "multiple choice",
    "year": 2018,
    "content": "<div>This is the question of all questions</div>",
    "options": [
        {
            "label": "b",
            "content": "option content",
            "is_correct": true
        },
        {
            "label": "b",
            "content": "option content",
            "is_correct": false
        }
    ]
}

It will be easier to use the above suggestion when updating or if the options are sent separately after creating the question so that by then we can have the question_id available.

ms1987's avatar

Ah okay, that was not clear to me. The way I see it, you have 2 options.

  • You save the question first with a separate call, this way you have a question_id available, but at the cost of an addtional (ajax) request.
  • A better option to me, seems to create a custom rule (information here: https://laravel.com/docs/8.x/validation#custom-validation-rules). This rule would then take in the full options array (or even the full questions, depending on the structure of your request) and just loop over the options to validate that you dont have the same labels. I would then pay some attention to making sure that you can return a useful validation message to your frontend. so that you can provide some insight to your user on which label was duplicated.

Does this help?

fcode's avatar
Level 7

Thanks for the tip. Is it possible to provide a sample code of how you might implement the second option? The options have to be sent together with the question in one request.

I'm still new to Laravel and PHP. I have read the documentation for custom rules but I still can't wrap my head around it fully.

ms1987's avatar
ms1987
Best Answer
Level 1

Here we go :-) First you make a request that validates your input. It could look something like this

class DummyRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'question.options' => [
                new OptionsRule(),
            ]
        ];
    }
}

Then you create a new Rules object (you can do this via php artisan make:rule OptionsRule)

The rule could look something like this

class OptionsRule implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        $allValues = collect($value);
        foreach ($value as $option) {
            $allValues->push($option['label']);
        }

        return $allValues->count() === $allValues->unique->count();
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The validation error message.';
    }
}

This is a very shallow implementation that will just loop over $value. $value is an array of all options for the question that came in via the request.

Here i just push all labels into a new collection and then compare the count of the entire collection (including possible duplicates) to the unique values (more information on the Collection methods in laravel can be found here: https://laravel.com/docs/8.x/collections#available-methods

My implementation is a bit shallow in the sense that i dont keep track of duplicated values. So rendering a descriptive message to the user might be a bit tricky now. This could easily be solved by keeping track of the duplicated values separately for example.

the message() method in the OptionsRule is responsible for rendering/returning the validation message to the user.

Hope that this can help getting you started ;-)

1 like
fcode's avatar
Level 7

Sorry, I haven't been able to try it. I've been busy. I will do so when I did try it.

Tray2's avatar

@fcode you can use constraints combined with several columns.

$table->unique(['label', 'question_id']);
1 like
fcode's avatar
Level 7

So that means that the constraint will only apply to the labels for a particular question_id correct? Can you provide some explanation as to how it works? And how do I account for this in the FormRequest validation rules?

jeevamugunthan's avatar

@fcode

try this under your validation rules

$question_id=$request['question_id']

'label ' => ['required','unique:options,label ,NULL,id,question_id,'.$question_id],

Please or to participate in this conversation.