mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
fix: Validation hooks mutate value before dehydration (#19300)
* fix: Validation hooks mutate value before dehydration * Update HasState.php
This commit is contained in:
@@ -284,6 +284,36 @@ trait HasState
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the raw Livewire state and prunes it to only contain keys
|
||||
* present in the template array. This preserves the sparse structure
|
||||
* of validated data while using clean (unmutated) values from Livewire.
|
||||
*
|
||||
* @param array<string, mixed> $source
|
||||
* @param array<string, mixed> $template
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function pruneStateToMatchKeys(array $source, array $template): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($template as $key => $templateValue) {
|
||||
if (! array_key_exists($key, $source)) {
|
||||
$result[$key] = $templateValue;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($templateValue) && is_array($source[$key])) {
|
||||
$result[$key] = $this->pruneStateToMatchKeys($source[$key], $templateValue);
|
||||
} else {
|
||||
$result[$key] = $source[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> | null $state
|
||||
*/
|
||||
@@ -417,6 +447,34 @@ trait HasState
|
||||
return Component::withVisibilityCache(function () use ($shouldCallHooksBefore, $afterValidate): array {
|
||||
$state = $this->validate();
|
||||
|
||||
// `validate()` returns data that went through `prepareForValidation()`,
|
||||
// which applies `mutateStateForValidation()` mutations. Those mutations
|
||||
// should only affect validation rules, not the dehydrated output. Replace
|
||||
// the mutated values with clean Livewire data, preserving the validated
|
||||
// array's sparse key structure.
|
||||
$statePath = $this->getStatePath();
|
||||
$livewire = $this->getLivewire();
|
||||
|
||||
if (filled($statePath)) {
|
||||
$rawState = data_get($livewire, $statePath);
|
||||
|
||||
if (is_array($rawState)) {
|
||||
$validatedFormData = data_get($state, $statePath);
|
||||
|
||||
if (is_array($validatedFormData)) {
|
||||
data_set($state, $statePath, $this->pruneStateToMatchKeys($rawState, $validatedFormData));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$rawState = [];
|
||||
|
||||
foreach (array_keys($state) as $key) {
|
||||
$rawState[$key] = data_get($livewire, $key);
|
||||
}
|
||||
|
||||
$state = $this->pruneStateToMatchKeys($rawState, $state);
|
||||
}
|
||||
|
||||
if ($shouldCallHooksBefore) {
|
||||
$this->callBeforeStateDehydrated($state);
|
||||
|
||||
|
||||
@@ -859,6 +859,43 @@ test('dehydrated state can be mutated when parent schema is hidden and dehydrate
|
||||
]);
|
||||
});
|
||||
|
||||
test('`mutateStateForValidationUsing()` does not affect the dehydrated state', function (): void {
|
||||
$schema = Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
->components([
|
||||
Field::make($statePath = Str::random())
|
||||
->default($state = Str::random())
|
||||
->mutateStateForValidationUsing(fn ($state) => $state . '_MUTATED_FOR_VALIDATION'),
|
||||
])
|
||||
->fill();
|
||||
|
||||
invade($schema->getLivewire())->cacheSchema('form', $schema);
|
||||
|
||||
expect($schema->getState())
|
||||
->toBe([
|
||||
$statePath => $state,
|
||||
]);
|
||||
});
|
||||
|
||||
test('`mutateStateForValidationUsing()` does not affect the dehydrated state when using `dehydrateStateUsing()`', function (): void {
|
||||
$schema = Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
->components([
|
||||
Field::make($statePath = Str::random())
|
||||
->default($state = Str::random())
|
||||
->mutateStateForValidationUsing(fn ($state) => $state . '_MUTATED_FOR_VALIDATION')
|
||||
->dehydrateStateUsing(fn ($state) => $state . '_DEHYDRATED'),
|
||||
])
|
||||
->fill();
|
||||
|
||||
invade($schema->getLivewire())->cacheSchema('form', $schema);
|
||||
|
||||
expect($schema->getState())
|
||||
->toBe([
|
||||
$statePath => $state . '_DEHYDRATED',
|
||||
]);
|
||||
});
|
||||
|
||||
test('sibling state can be retrieved relatively from another component', function (): void {
|
||||
Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
|
||||
Reference in New Issue
Block a user