skip to Main Content

I tried some validation rule like: Boolean and Required file with nested array field, but always failing

for example, I tried creating form request like this:

<?php

namespace AppHttpRequestsTest;

use IlluminateFoundationHttpFormRequest;

class Test extends FormRequest
{
    public function validationData()
    {
        return [
            'booleanField' => $this->boolean("booleanField"),
            'fileField' => $this->file("fileField"),
            'arrayField' => $this->input("arrayField"),
            'arrayField.*.booleanField' => $this->boolean("arrayField.*.booleanField"),
            'arrayField.*.fileField' => $this->file("arrayField.*.fileField"),
        ];
    }

    public function rules(): array
    {
        return [
            "booleanField" => ["required", "boolean"], // <= works as expected
            "fileField" => ["required", "file", "mimes:jpg,png,jpeg,docx,xlsx,zip", "max:5120"], // <= works as expected
            "arrayField" => ["required", "array"],
            "arrayField.*.booleanField" => ["required", "boolean"], // <= not working, always returning error "The arrayField.0.booleanField field must be true or false."
            "arrayField.*.fileField" => ["required", "file", "mimes:jpg,png,jpeg,docx,xlsx,zip", "max:5120"], // <= not working, always returning error "The arrayField.0.fileField is required."
        ];
    }
}

that’s what I found. I don’t know if any other rules also not working.

Laravel version 11.31.0.
Thank you.

duplicated question from #53489

2

Answers


  1. Chosen as BEST ANSWER

    The base problem is from client request to my API that using Content-Type: multipart/form-data header

    After many hours workaround and based on explanation given by @IGP. This is the solution (probably).

    reworked my FormRequest class:

    <?php
    
    namespace AppHttpRequestsTest;
    
    use IlluminateFoundationHttpFormRequest;
    
    class Test extends FormRequest
    {
        public function castData() // reworked from my previous validationData()
        {
            return [
                'booleanField' => "boolean",
                'fileField' => "file",
                'arrayField' => "input",
                'arrayField.*.booleanField' => "boolean",
                'arrayField.*.fileField' => "file",
            ];
        }
    
        public function rules(): array
        {
            return [
                "booleanField" => ["required", "boolean"],
                "fileField" => ["required", "file", "mimes:jpg,png,jpeg,docx,xlsx,zip", "max:5120"],
                "arrayField" => ["required", "array"],
                "arrayField.*.booleanField" => ["required", "boolean"],
                "arrayField.*.fileField" => ["required", "file", "mimes:jpg,png,jpeg,docx,xlsx,zip", "max:5120"],
            ];
        }
    
        // I created this custom function below to handle prepareForValidation
        protected function prepareForValidation(): void
        {
            if(method_exists($this, "castData")){
                $this->merge($this->resolveCasts($this->all(), $this->castData()));
            }
        }
    
        private function resolveCasts(array $data, array $castData, &$discoveredDataKey = null)
        {
            return Arr::map($data, function($value, $key) use ($castData, $discoveredDataKey){
                $discoveredDataKey = ($discoveredDataKey !== null ? $discoveredDataKey.'.' : null).$key;
                if(is_array($value)){
                    return $this->resolveCasts($value, $castData, $discoveredDataKey);
                }else{
                    $getCast = Arr::first(Arr::where($castData, function($castValue, $castKey) use ($discoveredDataKey) {
                        return Str::replaceMatches('/.d+/', '.*', $discoveredDataKey) === $castKey;
                    }));
                    return $this->{$getCast ?: "input"}($discoveredDataKey);
                }
            });
        }
    
    }
    

    Now all working as expected.

    Maybe not the best for performance. You can always improve that.

    Thank you... I hope this helps someone with similar case


  2. The method validationData is supposed to be a way for you to access the data that is going to be validated. It’s not meant to override data.

    When a FormRequest is resolved, the application (or container if you prefer) calls its validateResolved method.

    public function validateResolved()
    {
        $this->prepareForValidation();
    
        if (! $this->passesAuthorization()) {
            $this->failedAuthorization();
        }
    
        $instance = $this->getValidatorInstance();
    
        if ($this->isPrecognitive()) {
            $instance->after(Precognition::afterValidationHook($this));
        }
    
        if ($instance->fails()) {
            $this->failedValidation($instance);
        }
    
        $this->passedValidation();
    }
    

    If you want to modify the data that is about to get validated, the method you’re looking for is prepareForValidation. And to my knowledge you can’t use * like a wildcard there.

    class Test extends FormRequest
    {
    
        /**
         * Prepare the data for validation.
         *
         * @return void
         */
        public function prepareForValidation(): void
        {
            // please check this and make sure the data you're sending looks like the data you're trying to validate.
            dd($this->validationData());
    
            $this->merge([
               'key' => 'value,
               ...
            ]);
        }
    
        ...
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search