*/ public array $componentFileAttachments = []; #[Locked] public bool $areFormStateUpdateHooksDisabledForTesting = false; /** * @var array */ protected ?array $cachedForms = null; protected bool $hasCachedForms = false; protected bool $isCachingForms = false; protected ?ComponentContainer $currentlyValidatingForm = null; protected bool $hasFormsModalRendered = false; /** * @var array */ protected array $oldFormState = []; public function dispatchFormEvent(mixed ...$args): void { foreach ($this->getCachedForms() as $form) { $form->dispatchEvent(...$args); } } /** * @param array $state */ public function fillFormDataForTesting(array $state = []): void { if (! app()->runningUnitTests()) { return; } foreach (Arr::dot($state) as $statePath => $value) { $this->updatingInteractsWithForms($statePath); data_set($this, $statePath, $value); $this->updatedInteractsWithForms($statePath); } foreach ($state as $statePath => $value) { if (! is_array($value)) { continue; } $this->unsetMissingNumericArrayKeys($this->{$statePath}, $value); } } /** * @param array $target * @param array $state */ protected function unsetMissingNumericArrayKeys(array &$target, array $state): void { foreach ($target as $key => $value) { if (is_numeric($key) && (! array_key_exists($key, $state))) { unset($target[$key]); continue; } if (is_array($value) && is_array($state[$key] ?? null)) { $this->unsetMissingNumericArrayKeys($target[$key], $state[$key]); } } } public function getFormComponentFileAttachment(string $statePath): ?TemporaryUploadedFile { return data_get($this->componentFileAttachments, $statePath); } #[Renderless] public function getFormComponentFileAttachmentUrl(string $statePath): ?string { foreach ($this->getCachedForms() as $form) { if ($url = $form->getComponentFileAttachmentUrl($statePath)) { return $url; } } return null; } /** * @return array */ #[Renderless] public function getFormSelectOptionLabels(string $statePath): array { foreach ($this->getCachedForms() as $form) { if ($labels = $form->getSelectOptionLabels($statePath)) { return $labels; } } return []; } #[Renderless] public function getFormSelectOptionLabel(string $statePath): ?string { foreach ($this->getCachedForms() as $form) { if ($label = $form->getSelectOptionLabel($statePath)) { return $label; } } return null; } /** * @return array */ #[Renderless] public function getFormSelectOptions(string $statePath): array { foreach ($this->getCachedForms() as $form) { if ($results = $form->getSelectOptions($statePath)) { return $results; } } return []; } /** * @return array */ #[Renderless] public function getFormSelectSearchResults(string $statePath, string $search): array { foreach ($this->getCachedForms() as $form) { if ($results = $form->getSelectSearchResults($statePath, $search)) { return $results; } } return []; } public function deleteUploadedFile(string $statePath, string $fileKey): void { foreach ($this->getCachedForms() as $form) { $form->deleteUploadedFile($statePath, $fileKey); } } /** * @return array | null */ #[Renderless] public function getFormUploadedFiles(string $statePath): ?array { foreach ($this->getCachedForms() as $form) { if ($files = $form->getUploadedFiles($statePath)) { return $files; } } return null; } public function removeFormUploadedFile(string $statePath, string $fileKey): void { foreach ($this->getCachedForms() as $form) { $form->removeUploadedFile($statePath, $fileKey); } } public function reorderFormUploadedFiles(string $statePath, array $fileKeys): void { foreach ($this->getCachedForms() as $form) { $form->reorderUploadedFiles($statePath, $fileKeys); } } /** * @param array> | null $rules * @param array $messages * @param array $attributes * @return array */ public function validate($rules = null, $messages = [], $attributes = []): array { try { return parent::validate($rules, $messages, $attributes); } catch (ValidationException $exception) { $this->onValidationError($exception); $this->dispatch('form-validation-error', livewireId: $this->getId()); throw $exception; } } /** * @param string $field * @param array> $rules * @param array $messages * @param array $attributes * @param array $dataOverrides * @return array */ public function validateOnly($field, $rules = null, $messages = [], $attributes = [], $dataOverrides = []) { try { return parent::validateOnly($field, $rules, $messages, $attributes, $dataOverrides); } catch (ValidationException $exception) { $this->onValidationError($exception); $this->dispatch('form-validation-error', livewireId: $this->getId()); throw $exception; } } protected function onValidationError(ValidationException $exception): void {} /** * @param array $attributes * @return array */ protected function prepareForValidation($attributes): array { if ($this->currentlyValidatingForm) { $attributes = $this->currentlyValidatingForm->mutateStateForValidation($attributes); } else { foreach ($this->getCachedForms() as $form) { $attributes = $form->mutateStateForValidation($attributes); } } return $attributes; } /** * @return class-string | null */ public function getFilamentTranslatableContentDriver(): ?string { return null; } public function makeFilamentTranslatableContentDriver(): ?TranslatableContentDriver { $driver = $this->getFilamentTranslatableContentDriver(); if (! $driver) { return null; } return app($driver, ['activeLocale' => $this->getActiveFormsLocale() ?? app()->getLocale()]); } public function getActiveFormsLocale(): ?string { return null; } public function updatingInteractsWithForms(string $statePath): void { $statePath = (string) str($statePath)->before('.'); // https://github.com/filamentphp/filament/pull/13973 $this->oldFormState[$statePath] ??= data_get($this, $statePath); } public function getOldFormState(string $statePath): mixed { return data_get($this->oldFormState, $statePath); } public function updatedInteractsWithForms(string $statePath): void { if (app()->runningUnitTests() && $this->areFormStateUpdateHooksDisabledForTesting) { return; } foreach ($this->getCachedForms() as $form) { $form->callAfterStateUpdated($statePath); } } public function disableFormStateUpdateHooksForTesting(): void { if (! app()->runningUnitTests()) { return; } $this->areFormStateUpdateHooksDisabledForTesting = true; } public function enableFormStateUpdateHooksForTesting(): void { if (! app()->runningUnitTests()) { return; } $this->areFormStateUpdateHooksDisabledForTesting = false; } protected function cacheForm(string $name, Form | Closure | null $form): ?Form { $this->isCachingForms = true; $form = value($form); if ($form) { $this->cachedForms[$name] = $form; } else { unset($this->cachedForms[$name]); } $this->isCachingForms = false; return $form; } /** * @return array */ protected function cacheForms(): array { $this->isCachingForms = true; $this->cachedForms = collect($this->getForms()) ->merge($this->getTraitForms()) ->mapWithKeys(function (Form | string | null $form, string | int $formName): array { if ($form === null) { return ['' => null]; } if (is_string($formName)) { return [$formName => $form]; } if (! method_exists($this, $form)) { $livewireClass = $this::class; throw new Exception("Form configuration method [{$form}()] is missing from Livewire component [{$livewireClass}]."); } return [$form => $this->{$form}($this->makeForm())]; }) ->forget('') ->all(); $this->isCachingForms = false; $this->hasCachedForms = true; foreach ($this->mountedFormComponentActions as $actionNestingIndex => $actionName) { $this->cacheForm( "mountedFormComponentActionForm{$actionNestingIndex}", $this->getMountedFormComponentActionForm($actionNestingIndex), ); } return $this->cachedForms; } /** * @return array */ public function getTraitForms(): array { $forms = []; foreach (class_uses_recursive($class = static::class) as $trait) { if (method_exists($class, $method = 'get' . class_basename($trait) . 'Forms')) { $forms = [ ...$forms, ...$this->{$method}(), ]; } } return $forms; } protected function hasCachedForm(string $name): bool { return array_key_exists($name, $this->getCachedForms()); } public function getForm(string $name): ?Form { return $this->getCachedForms()[$name] ?? null; } /** * @return array */ public function getCachedForms(): array { if (! $this->hasCachedForms) { return $this->cacheForms(); } return $this->cachedForms; } /** * @return array */ protected function getForms(): array { return [ 'form', ]; } public function form(Form $form): Form { return $form ->schema($this->getFormSchema()) ->model($this->getFormModel()) ->statePath($this->getFormStatePath()) ->operation($this->getFormContext()); } /** * @deprecated Override the `form()` method to configure the default form. */ protected function getFormModel(): Model | string | null { return null; } /** * @deprecated Override the `form()` method to configure the default form. * * @return array */ protected function getFormSchema(): array { return []; } /** * @deprecated Override the `form()` method to configure the default form. */ protected function getFormContext(): ?string { return null; } /** * @deprecated Override the `form()` method to configure the default form. */ protected function getFormStatePath(): ?string { return null; } /** * @return array> */ public function getRules(): array { $rules = parent::getRules(); foreach ($this->getCachedForms() as $form) { $rules = [ ...$rules, ...$form->getValidationRules(), ]; } return $rules; } /** * @return array */ protected function getValidationAttributes(): array { $attributes = parent::getValidationAttributes(); foreach ($this->getCachedForms() as $form) { $attributes = [ ...$attributes, ...$form->getValidationAttributes(), ]; } return $attributes; } protected function makeForm(): Form { return Form::make($this); } public function isCachingForms(): bool { return $this->isCachingForms; } public function mountedFormComponentActionInfolist(): Infolist { return $this->getMountedFormComponentAction()->getInfolist(); } public function currentlyValidatingForm(?ComponentContainer $form): void { $this->currentlyValidatingForm = $form; } }