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

automica's avatar

Validation of belongsToMany

Following on from my question earlier, https://laracasts.com/discuss/channels/laravel/best-practice-for-crud-updating-an-existing-belongstomany-in-api

I have now decided on the following structure for my endpoint

curl --request PATCH \
    "http://connect.lndo.site/api/custom_forms/crud/bff1f8e1-bccf-61e8-4ed4-9c53130e9d51/relationships/items" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "[
    {
        \"type\": \"items\",
        \"id\": \"bff1f8e1-bccf-61e8-4ed4-9c53130e9d52\"
    },
    {
        \"type\": \"items\",
        \"id\": \"bff1f8e1-bccf-61e8-4ed4-9c53130e9d51\"
    }
]"

The question I've got now, is how would I go about validating the payload I'm passing to ensure id's are unique and we have a type being passed in too?

I've tried the following:

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            '*' => 'array',
            '*.type' => 'required|string',
            '*.id' => 'required|string'
        ];
    }

but I'm not getting the 422 response I was expecting when passing in nothing in my body.

Documents suggest adding a key

        return [
            'items.*.type' => 'required|string',
            'items.*.id' => 'required|string'
        ];

but it feels like I should be able to do this without modifying the request body.

0 likes
11 replies
newbie360's avatar

@automica

clone the content to new variable ?

protected function prepareForValidation()
{
    $this->merge([
        'items' => $this->getContent(),
    ]);
}

public function rules(): array
{
    dd($this->items);
    
    ...
}
Snapey's avatar

but I'm not getting the 422 response I was expecting when passing in nothing in my body.

probably because you receive an empty array? Perhaps if its not actually a redundant check, you could create a simple rule that counts the array?

automica's avatar

@Snapey

It does appear I'm getting an empty array.

so with prepareForValidation method to take the RAW content and add it $this as data :

curl --location --request POST 'http://connect.lndo.site/api/custom_forms/crud/ewew/relationships/items' \
--header 'Content-Type: application/json' \
--data-raw '[]'

within my request:

    public function rules(): array
    {
       dump($this->data);

        return [
            'data.*' => 'array',
            'data.*.type' => 'required|string',
            'data.*.id' => 'required|string'
        ];
    }

    protected function prepareForValidation()
    {
        $this->merge([
            'id' => $this->route('id'),
            'data' => $this->getContent()
        ]);
    }

The dump does return [] but its still not failing validation.

with the following:

curl --location --request POST 'http://connect.lndo.site/api/custom_forms/crud/ewew/relationships/items' \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "id": "cb3b8781-a911-61ec-1b91-9c7619f2b19d"
    }
]'

its should fail because I've removed type key from object.

I think its still passing data as a string for when it gets to the controller, as I've got to do:

    $data = json_decode($request->except('id')['data']);

before I can do anything with the data.

any ideas?

newbie360's avatar

@automica

** no attributes name

https://example.com/[item]=1

    public function rules(): array
    {
       dump($this->validationData()); // empty
    }

this make sense, because Laravel can't try to guess the attributes name for you or some security reason

    protected function prepareForValidation()
    {
        $data = $this->getContent();
        
        // format the body content to meet the validation structure
        $data = array(......);
        
        $this->merge([
            'data' => $data,
        ]);
    }
automica's avatar

@newbie360 thanks. I'm creeping closer...

with

curl --location --request POST 'http://connect.lndo.site/api/custom_forms/crud/ewew/relationships/items' \
--header 'Content-Type: application/json' \
--data-raw '[
    {
        "id": "cb3b8781-a911-61ec-1b91-9c7619f2b19d"
    }
]'

and:

    public function rules(): array
    {
        dump($this->validationData()['data']);

        return [
            'data.*.type' => 'required|string|size:5',
            'data.*.id' => 'required|string|size:36'
        ];
    }

    protected function prepareForValidation()
    {
        $data = collect(json_decode($this->getContent()));

        $this->merge([
            'id' => $this->route('id'),
            'data' => $data->map(function ($row) {
                return (array)$row;
            })
        ]);
    }

I can see the data in the dump

Illuminate\Support\Collection {#1316
  #items: array:1 [
    0 => array:1 [
      "id" => "cb3b8781-a911-61ec-1b91-9c7619f2b19d"
    ]
  ]
}

but its still failing to validate the parts of this array.

automica's avatar

@newbie360 thanks for that spot buts not responding to anything I put in rules.

edit, if I change the 'id' to 'idx' then I get a validation error.

  public function rules(): array
    {
        return [
            'idx' => 'required',
            'data.*.type' => 'required|string|size:5',
            'data.*.id' => 'required|string|size:36'
        ];
    }

    protected function prepareForValidation()
    {
        $data = collect(json_decode($this->getContent()));

        $this->merge([
            'id' => $this->route('id'),
            'data' => $data->map(function ($row) {
                return (array)$row;
            })
        ]);
    }

so it appears my issue is with

            'data.*.type' => 'required|string|size:5',
            'data.*.id' => 'required|string|size:36'

to check array keys.

The above looks like it should work out of the box, but for now I can do:

    public function rules(): array
    {
        $rules = ['id' => 'required'];

        foreach ($this->data as $index => $data) {
            $rules['data.' . $index . '.type'] = 'required|string|size:5';
            $rules['data.' . $index . '.id'] = 'required|string|size:36';
        }
        
        return $rules;
    }
newbie360's avatar

@automica

weird, look the data structure is correct, and there just use string key, can you try add one rule

        return [
            //'idx' => 'required',
            'data' => 'array',
            'data.*.type' => 'required|string|size:5',
            'data.*.id' => 'required|string|size:36'
        ];
automica's avatar

@newbie360

with

      return [
            'data' => 'array',
            'data.*.type' => 'required|string|size:5',
            'data.*.id' => 'required|string|size:36'
        ];

all my validation fails. it appears 'data' => 'array', is the culprit

but if I hard code 0 instead of *

        $rules = [
            'id' => 'required',
            'data.0.type' => 'required|string|size:5',
            'data.0.id' => 'required|string|size:36'
        ];

then all my validation works.

newbie360's avatar

@automica

can you dump($this->data) with id and type

after that use this rule only

        return [
            'data.*.type' => 'required|string|size:5',
            'data.*.id' => 'required|string|size:36'
        ];

and add a method in the form request

    public function withValidator($validator)
    {
        $validator->setImplicitAttributesFormatter(function ($attribute) {
            [$field, $key, $value] = explode('.', $attribute);

            if ($field == 'data') {
                return sprintf('data error element:%d value:%s', $key +1, $value);
            }
        });
    }

any response ?

automica's avatar

@newbie360 I've done that but I'm back to where I am earlier when its ignoring the rules with * in them.

interestingly, adding a dump inside the closure doesn't return any result which suggests setImplicitAttributesFormatter isn't being triggered.

    /**
     * Configure the validator instance.
     *
     * @param \Illuminate\Validation\Validator $validator
     * @return void
     */
    public function withValidator(\Illuminate\Validation\Validator $validator)
    {
        $validator->setImplicitAttributesFormatter(function ($attribute) {
		
	dump($attribute); 
            
            [$field, $key, $value] = explode('.', $attribute);

            if ($field == 'data') {
                return sprintf('data error element:%d value:%s', $key +1, $value);
            }
        });
    }

BTW i'm in laravel 7, and a rather worked version of it (existing client project), so I'm prepared to think there could be some oddness elsewhere in my environment.

would be good to know though.

Please or to participate in this conversation.