Formuláře
Životní cyklus ukládání
Metoda Form::save() vykonává striktní 9-krokovou pipeline. Každý krok je jasně definovaný s hooky pro přizpůsobení.
Na této stránce
- Přehled pipeline
- Krok 1: Validace
- Krok 2: Mutace dat
- Více mutací
- Krok 3: Plugin hook — form.saving
- Krok 4: Before save
- Krok 5: Perzistence
- Výchozí chování
- Vlastní perzistence
- Žádný model
- Optimistic locking
- Krok 6: Uložení relací
- Krok 7: After save
- Krok 8: Plugin hook — form.saved
- Krok 9: Notifikace
- Kompletní příklad
- Ošetření chyb
Metoda Form::save() vykonává striktní 9-krokovou pipeline. Každý krok je jasně definovaný s hooky pro přizpůsobení.
Tato stránka popisuje, co se stane při uložení formuláře.
Přehled pipeline
Form::save()│├── 1. VALIDACE│ ├── Posbírat pravidla ze všech polí│ ├── Sloučit pravidla na úrovni formuláře│ ├── Projít přes ValidationPipeline│ └── Při selhání vyhodit ValidationException ← STOP│├── 2. MUTACE│ └── mutateDataBeforeSave(Closure $fn)│ Transformovat zvalidovaná data před perzistencí│├── 3. PLUGIN HOOK: form.saving│ └── Pluginy mohou prohlédnout nebo upravit $data│├── 4. BEFORE SAVE│ └── beforeSave(Closure $fn)│ Void hook — side efekty, externí volání│├── 5. PERZISTENCE│ ├── Výchozí: Model::create($data) nebo $model->update($data)│ └── Vlastní: using(Closure $fn)│├── 6. ULOŽENÍ RELACÍ│ └── RelationshipSaveHandler kaskáduje Repeater data do relací modelu│├── 7. AFTER SAVE│ └── afterSave(Closure $fn)│ Void hook — side efekty, vyčištění cache, události│├── 8. PLUGIN HOOK: form.saved│ └── Pluginy pozorují perzistovaný $record│└── 9. NOTIFIKACE ├── Odeslat úspěšnou notifikaci přes Notifications modul └── Přeskočit při disableSuccessNotification()
Krok 1: Validace
Posbírá pravidla ze všech field komponent a zvaliduje aktuální stav.
// Automatické v save()// Lze také zavolat samostatně:$data = $form->validate();
Pokud validace selže, vyhodí se Illuminate\Validation\ValidationException. Kroky 2-9 se úplně přeskočí.
Detaily o pravidlech polí, vlastních zprávách a ValidationPipeline viz Validace.
Krok 2: Mutace dat
Transformujte zvalidovaná data, než se dostanou k modelu:
$form->mutateDataBeforeSave(function (array $data): array { // Slugify titulku $data['slug'] = Str::slug($data['title']); // Odstranit dočasná pole unset($data['agree_to_terms']); // Zašifrovat citlivá data $data['ssn'] = encrypt($data['ssn']); return $data; // MUSÍ vrátit pole});
Closura dostane pole zvalidovaných dat a musí vrátit upravené pole.
Více mutací
$form ->mutateDataBeforeSave(fn (array $data) => array_merge($data, [ 'updated_by' => auth()->id(), ]));
Krok 3: Plugin hook — form.saving
Vystřelí automaticky, když jsou registrované pluginy přes PluginManager. Pluginy mohou prohlédnout nebo upravit $data před perzistencí. Uživatelský kód s tímto krokem přímo neinteraguje.
Krok 4: Before save
Void hook, který běží po mutaci, ale před perzistencí:
$form->beforeSave(function (array $data): void { // Ověřit dostupnost externí služby if (! ExternalApi::isAvailable()) { throw new \RuntimeException('External service is down'); } // Odeslat pre-save událost event(new UserSaving($data));});
Closura dostane zmutovaná data, ale nevrací je.
Pokud tento hook vyhodí výjimku, perzistence (krok 5) se přeskočí.
Krok 5: Perzistence
Výchozí chování
Logika perzistence závisí na režimu modelu:
// Create mód — model je řetězec třídy$form->model(User::class);// → User::create($data) // Edit mód — model je instance$form->model($user);// → $user->update($data)
Vlastní perzistence
Přepište výchozí pomocí using():
$form->using(function (array $data): mixed { // Vytvořit $user = User::create($data); $user->assignRole($data['role']); return $user;});
Callback using() nahrazuje celou výchozí create/update logiku. Dostane $data (pole zmutovaných dat). Návratová hodnota se stane výsledkem save().
Relační repeatery a using(). $data obsahuje hodnotu každého pole, včetně relačních Repeater polí (např. klíč children pro Repeater::make('children')->relationship('children')). Výchozí cesta perzistence tyto klíče před zápisem rodiče odstraní; using() ne. Takže:
- Neprovádějte hromadné přiřazení celého
$data—User::create($data)by se pokusil zapsatchildrenjako sloupec. Přiřaďte jen vlastní atributy rodiče. - Vraťte perzistovaný
Modela kaskáda relací (krok 6) stále poběží a uloží za vás řádky repeateru do relace:
$form->using(fn (array $data) => User::create(['name' => $data['name']]));// → řádky children repeateru se nakaskádují na $user->children()
- Vraťte cokoli jiného než
Model(id, DTO, výsledek příkazu) a kaskáda se přeskočí — váš callback vlastní perzistenci úplně, včetně relací.
Žádný model
Pokud je nastaveno model(null) a není poskytnut callback using(), save() vyhodí InvalidArgumentException.
Optimistic locking
Souběžné editace se mohou tiše navzájem přepsat: dva uživatelé otevřou stejný záznam, oba uloží a druhý zápis přepíše první. Zapněte kontrolu verze pomocí optimisticLock():
$form->model($order)->optimisticLock(); // výchozí sloupec 'updated_at'$form->model($order)->optimisticLock('version'); // nebo libovolný integer/version sloupec
Když je zapnuto, hodnota lock sloupce se zachytí při naplnění formuláře ze záznamu a nese se přes Livewire round trip. Při uložení se znovu načte aktuální databázová hodnota; pokud už neodpovídá zachycené baseline — někdo jiný záznam mezitím uložil nebo smazal — uložení se přeruší s NyonCode\WireForms\Forms\Runtime\StaleModelException a konfliktní notifikací (wire-forms::messages.stale), přičemž novější data zůstanou nedotčená.
- Opt-in a zpětně kompatibilní — bez
optimisticLock()se nic nemění. - Běží jen v režimu update (existující model).
- Nastavte
->model($record)před->fill(), aby se baseline mohla zachytit; pokud baseline chybí, kontrola selže open (uložení nezablokuje). - Odchyťte
StaleModelException, pokud chcete konflikt řešit sami (např. znovu načíst a znovu předložit formulář):
use NyonCode\WireForms\Forms\Runtime\StaleModelException; try { $this->form->save();} catch (StaleModelException $e) { // $e->model, $e->lockColumn — znovu načíst a nechat uživatele zkusit znovu}
Krok 6: Uložení relací
Po perzistenci modelu RelationshipSaveHandler nakaskáduje data Repeater polí do relací modelu. Tento krok běží jen když je výsledek perzistence instance Eloquent Model — což zahrnuje Model vrácený z vlastního callbacku using() (viz Vlastní perzistence).
Uživatelský kód s tímto krokem přímo neinteraguje; řeší se automaticky pro Repeater pole s nakonfigurovaným ->relationship().
Krok 7: After save
Void hook, který běží po úspěšné perzistenci:
$form->afterSave(function (mixed $record): void { // $record je vytvořený/aktualizovaný Model (nebo návratová hodnota using()) Cache::forget("user:{$record->id}"); // Odeslat událost event(new UserSaved($record)); // Odeslat notifikaci $record->notify(new WelcomeNotification());});
Dostane $record — návratovou hodnotu kroku perzistence (typicky instanci Modelu).
Krok 8: Plugin hook — form.saved
Vystřelí po afterSave, aby pluginy mohly pozorovat perzistovaný záznam. Uživatelský kód s tímto krokem přímo neinteraguje.
Krok 9: Notifikace
Odešle úspěšnou notifikaci přes Notifications modul:
// Vlastní zpráva$form->successMessage('User saved successfully!'); // Vypnout úplně$form->disableSuccessNotification();
Notifikace se odešle přes NotificationManager s aktivním driverem (session, Livewire, Flasher atd.).
Tento krok vystřelí jen když:
- Notifications modul je dostupný (kontrola
app()->bound()) disableSuccessNotification()NEBYLO zavoláno- Uložení proběhlo bez výjimek
Kompletní příklad
class EditUser extends Component{ use WithForms; public User $user; public array $data = []; public function mount(User $user): void { $this->user = $user; $this->form->fill($user->toArray()); } public function form(Form $form): Form { return $form ->statePath('data') ->model($this->user) ->schema([ TextInput::make('name')->required()->maxLength(255), TextInput::make('email')->email()->required(), Select::make('role') ->options(['admin' => 'Admin', 'editor' => 'Editor']) ->required(), Toggle::make('active'), ]) ->mutateDataBeforeSave(function (array $data): array { $data['updated_by'] = auth()->id(); return $data; }) ->beforeSave(function (array $data): void { Log::info('Updating user', ['id' => $this->user->id]); }) ->afterSave(function (mixed $record): void { Cache::forget("user:{$record->id}"); event(new UserUpdated($record)); }) ->successMessage('User updated.'); } public function save(): void { $this->form->save(); $this->redirect(route('users.index')); }}
Ošetření chyb
| Výjimka | Kdy | Efekt |
|---|---|---|
ValidationException |
Krok 1 selže | Kroky 2-9 přeskočeny, chyby zobrazeny v UI |
Jakýkoli Throwable |
Kroky 2-8 vyhodí | Pipeline se přeruší, žádná notifikace |
InvalidArgumentException |
Žádný model + žádný using() |
Krok 5 selže |
Save pipeline se ve výchozím stavu neobaluje do databázové transakce. Pokud potřebujete atomicitu, obalte do DB::transaction():
public function save(): void{ DB::transaction(fn () => $this->form->save());}