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 App\Http\Requests\Test;
use Illuminate\Foundation\Http\FormRequest;
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
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 App\Http\Requests\Test;
use Illuminate\Foundation\Http\FormRequest;
class Test extends FormRequest
{
public function castData() // reworked from my previous validationData()
{
return [
'booleanField' => [
"type" => "boolean",
"default" => false,
],
'nullableBooleanField' => [
"nullable" => true,
"type" => "boolean",
],
'fileField' => [
"type" => "file",
],
'arrayField' => [
"type" => "input",
],
'arrayField.*.booleanField' => [
"type" => "boolean",
"default" => false,
],
'arrayField.*.fileField' => [
"type" => "file",
],
];
}
public function rules(): array
{
return [
"booleanField" => ["required", "boolean"],
"nullableBooleanField" => ["nullable", "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->setDefaultToMissingData(
$this->resolveCasts(
$this->all(),
$this->castData()
),
Arr::where(Arr::map($this->castData(), function($value,$key){
return Arr::get($value, 'default');
}), function($value){
return !is_null($value);
})
)
);
}
}
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(Arr::accessible($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;
}));
$getValue = $this->{Arr::get($getCast, "type", "input")}($discoveredDataKey, Arr::get($getCast, "default"));
if(Arr::get($getCast, "nullable", false)){
$nullableValue = $this->input($discoveredDataKey);
}
$value = isset($nullableValue) ? (
is_null($nullableValue) ? null : $getValue
) : $getValue;
return $value;
}
});
}
private function setDefaultToMissingData($data, $casts) {
foreach ($casts as $cast => $value) {
$data = $this->setDataValueToDefaultIfNotExists($data, $cast, $value);
}
return $data;
}
private function setDataValueToDefaultIfNotExists($data, $cast, $value) {
$keys = explode('.', $cast);
$current = &$data;
foreach ($keys as $index => $key) {
if ($key === '*') {
foreach ($current as &$subData) {
$subData = $this->setDataValueToDefaultIfNotExists($subData, implode('.', Arr::take($keys, $index + 1)), $value);
}
return $data;
}
if ($index === count($keys) - 1) {
if (!Arr::exists($current, $key)) {
$current[$key] = $value;
}
return $data;
}
if (!Arr::exists($current, $key) || !Arr::accessible($current[$key])) {
$current[$key] = [];
}
$current = &$current[$key];
}
return $data;
}
}
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