Hydration dan Dehydration
Hydration adalah proses mengubah model menjadi value untuk form. Dehydration adalah proses mengubah input yang sudah lolos validation kembali menjadi attribute yang akan ditulis. Gunakan halaman ini ketika field form dan column database tidak sepakat mengenai nama, type, atau apakah value tersebut memang boleh ditulis. Field state lifecycle menjelaskan urutan hook; halaman ini menjelaskan konversinya.
Contoh minimal
use App\Models\Post;
use PandaPanel\Forms\Components\TextInput;
use PandaPanel\Forms\FormSchema;
$post = Post::query()->find(1);
$schema = FormSchema::make()
->model(Post::class)
->forPage('edit')
->schema([
TextInput::make('title'),
TextInput::make('slug')->dehydrateTo('url_slug'),
]);
// Masuk: record menjadi value field.
$form = $schema->toArray($post);
$form['schema'][0]['value']; // 'Hello world'
// Keluar: input tervalidasi menjadi attributes.
$schema->dehydrate(['title' => 'Renamed', 'slug' => 'renamed'], $post);
// ['title' => 'Renamed', 'url_slug' => 'renamed']2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Hydration
Field::formValue(?Model $record): mixed menghasilkan value untuk satu field dengan urutan:
- Jika
$record === null—biasanya create page—value berasal daridefault(). - Jika ada record, value dibaca melalui
data_get($record, $name). Column biasa, accessor, dan cast attribute semuanya dapat bekerja tanpa hook tambahan. - Setelah itu framework menjalankan
formatUsing(). Jika tidak ada, framework memakaicastForForm()bawaan field type. - Terakhir
afterStateHydrated()dijalankan. Return value dari hook ini diabaikan.
Bentuk value yang dihasilkan setiap field type
castForForm() bersifat protected, jadi ini merupakan behavior internal, bukan public API. Behavior inilah yang membuat value datetime dapat langsung masuk ke control datetime-local tanpa menulis hook sendiri.
| Field | Input yang diterima | Value yang diterima control |
|---|---|---|
TextInput, Textarea | apa pun | ?string — null tetap null, selain itu di-cast ke string |
PasswordInput | apa pun | selalu null |
NumberInput, Slider | numeric | int|float, selain itu null |
Checkbox, Toggle | apa pun | bool |
Select (single) | string|int | value apa adanya, selain itu null |
Select (multiple) | array | list<string>, selain itu [] |
Radio | string|int | value apa adanya, selain itu null |
CheckboxList | array | list<string>, selain itu [] |
ToggleButtons | scalar atau array | mengikuti behavior Select |
DatePicker | CarbonInterface atau string | Y-m-d, selain itu null |
DateTimePicker | CarbonInterface atau string | Y-m-d\TH:i, atau memakai :s saat seconds() aktif |
TimePicker | CarbonInterface atau string | H:i, atau H:i:s saat seconds() aktif |
ColorPicker | string | string jika dapat diparse sebagai color, selain itu null |
TagsInput | array, atau string dengan separator() | list<string>, value kosong dibuang |
KeyValue | array atau JSON string | array<string, string>, key kosong dibuang |
MarkdownEditor | string | ?string |
CodeEditor | string atau array | string, atau array yang di-pretty-print menjadi JSON |
FileUpload | string, atau array saat multiple() | path, atau list<string> |
Repeater | array | list<array>, entry non-array dibuang |
Builder | array | list<array{type, data}>, entry dengan block yang tidak dideklarasikan dibuang |
HiddenInput, RichEditor, CustomField | apa pun | tidak diubah |
Select many-to-many merupakan pengecualian karena value-nya tidak berada pada attribute model, tetapi pada pivot table. Sebelum serialization, FormSchema::toArray() mengisinya melalui Select::relatedKeys($record).
Hook fill pada Page
fillForm() pada Resource Page membungkus hydration milik schema:
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Resources\Pages\EditRecord;
final class EditPost extends EditRecord
{
protected static string $resource = PostResource::class;
protected function beforeFill(): void
{
// Sebelum schema diserialisasi. Throw Halt untuk membatalkan Page.
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function mutateFormDataBeforeFill(array $data): array
{
// Semua value field, keyed by name, setelah hook field dijalankan.
return $data;
}
/**
* @param array<string, mixed> $data
*/
protected function afterFill(array $data): void
{
//
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mutateFormDataBeforeFill() menerima value field yang berada di bawah schema, pada kedalaman apa pun, dalam bentuk flattened map berdasarkan field name. Hasilnya diterapkan kembali ke serialized components. Field di dalam Wizard dan Tabs berada di bawah steps dan tabs, sehingga tidak masuk map tersebut. Gunakan hook ini ketika sebuah value bergantung pada lebih dari satu field. Jika hanya bergantung pada value field itu sendiri, gunakan formatUsing().
Dehydration
FormSchema::dehydrate(array $validated, ?Model $record = null): array mengubah validated input menjadi attributes yang akan ditulis Page. Enam pertanyaan menentukan apakah sebuah field ikut menghasilkan attribute:
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Forms\Components\Checkbox;
use PandaPanel\Forms\Components\TextInput;
use PandaPanel\Forms\FormSchema;
$schema = FormSchema::make()->schema([
TextInput::make('name'),
Checkbox::make('confirmation')->dehydrated(false),
]);
$schema->dehydrate(['name' => 'Apollo', 'confirmation' => true]);
// ['name' => 'Apollo']2
3
4
5
6
7
8
9
10
11
12
- Field visible pada Page ini (
isHiddenOn()). - Field memang boleh di-dehydrate (
isDehydrated($record)). - Field bukan bagian dari
Relationshipgroup—field relation ditulis setelah owner record. - Key field tersedia di
$validated. shouldDehydrate($value)mengizinkan value tersebut—ini adalah jawaban daridehydrateWhen().- Field bukan
Selectmany-to-many yang tidak memiliki column pada owner.
Field yang lolos disimpan menggunakan key dari getDehydrateKey() setelah value melewati mutate().
Memilih attribute tujuan
use PandaPanel\Forms\Components\Select;
use PandaPanel\Forms\Components\TextInput;
TextInput::make('slug')->dehydrateTo('url_slug'); // eksplisit
Select::make('author')->relationship('author', 'name');
// Field dinamai berdasarkan relation, tetapi dipersist ke `author_id`.
// Schema me-resolve foreign key dari model sehingga form tidak perlu menuliskannya sendiri.2
3
4
5
6
7
8
FormSchema::dehydrate() lebih dulu memanggil Select::foreignKeyFor($modelClass). Method ini hanya menghasilkan key untuk relation BelongsTo. Field lainnya menggunakan getDehydrateKey().
Data yang ditulis setelah owner record tersedia
FormSchema::saveRelations(Model $record, array $validated): void menangani dua jenis data yang tidak dapat ditulis bersamaan dengan attributes owner. Resource Page memanggilnya di dalam transaction yang sama dengan write utama:
$attributes = $schema->dehydrate($data, $record);
DB::transaction(function () use ($schema, $attributes, $data): void {
$post = Post::query()->create($attributes);
// Related records (HasOne, MorphOne, BelongsTo) dan pivot rows.
$schema->saveRelations($post, $data);
});2
3
4
5
6
7
8
HasOne child dan pivot row sama-sama memerlukan key dari owner yang belum ada sebelum owner berhasil disimpan. Karena itulah urutannya tidak dapat dibalik. Lihat Relationship forms.
Hook save pada Page
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Resources\Pages\CreateRecord;
final class CreatePost extends CreateRecord
{
protected static string $resource = PostResource::class;
/**
* @param array<string, mixed> $input
* @return array<string, mixed>
*/
protected function beforeValidate(array $input): array
{
return $input; // raw request sebelum rules dijalankan
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function afterValidate(array $data): array
{
return $data;
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function mutateFormDataBeforeCreate(array $data): array
{
return $data; // hanya create
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function mutateFormDataBeforeSave(array $data, ?Model $record): array
{
return $data; // create dan edit
}
/**
* @param array<string, mixed> $attributes
*/
protected function handleRecordCreation(array $attributes): Model
{
// Write utama setelah dehydration menghasilkan attributes.
return parent::handleRecordCreation($attributes);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Page hooks bekerja pada seluruh array, sedangkan field hooks bekerja pada satu value. mutateFormDataBeforeSave() berjalan sebelum dehydrate(), sehingga nama key yang terlihat masih nama field pada form—misalnya slug, bukan url_slug.
Membangun ulang schema menggunakan submitted state
FormSchema::toArrayWithState(?Model $record, array $state): array menyerialisasi schema lalu menimpa value setiap field menggunakan $state, termasuk field yang berada di dalam schema, steps, dan tabs. Hanya field yang benar-benar dideklarasikan schema yang dibaca dari state. Key tambahan yang tidak pernah menjadi field dibuang seperti saat submit biasa.
$form = $schema->toArrayWithState(null, ['name' => 'Apollo']);
$form['schema'][0]['value']; // 'Apollo'
$form['schema'][1]['value']; // value yang diberikan schema2
3
Response inilah yang dikembalikan endpoint form-state. Lihat Live fields.
Catatan
dehydrate()tidak pernah membaca request langsung. Method hanya menerima validated data. Key yang tidak dihasilkan rules tidak dapat masuk ke proses write.- Missing key berbeda dengan value
null. Field yang key-nya tidak ada di$validateddilewati, bukan ditulis sebagainull.Relationshipgroup melakukan distinction yang sama menggunakan path check karenadata_get()tidak dapat membedakan kedua kondisi tersebut. - Write menggunakan
forceFill().handleRecordCreation()danhandleRecordUpdate()melewati$fillable. Ini aman karena attribute list berasal dari schema, bukan langsung dari request. Override method tersebut jika write harus melalui service layer. Checkboxdefault kefalse, bukannull. Checkbox selalu hadir di payload sebagai true atau false. Karena iturequiredtetap lolos untuk checkbox yang tidak dicentang; rule sebenarnya untuk type adalahboolean, sedangkanaccepteddigunakan jika checkbox wajib dicentang.- Field di dalam relationship group tidak pernah masuk attributes owner. Field tersebut dieliminasi pada langkah ketiga, sehingga misalnya
profile.biotidak pernah salah ditulis ke columnbiomilik owner. - Hydration dijalankan pada setiap render, termasuk endpoint
form-state. ClosureformatUsing()yang menjalankan query harus siap dipanggil lebih dari sekali dalam satu Page interaction.