Junior: питання на співбесіді з теми «Форми й валідація»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
wire:submit на тегу <form> перехоплює відправку форми й викликає метод компонента замість звичайного POST-запиту з перезавантаженням сторінки.
new class extends Component {
public string $title = '';
public string $content = '';
public function save(): void
{
$validated = $this->validate([
'title' => 'required|min:3',
'content' => 'required',
]);
auth()->user()->posts()->create($validated);
$this->redirectRoute('posts.index');
}
};
<form wire:submit="save">
<input type="text" wire:model="title">
<textarea wire:model="content"></textarea>
<button type="submit">Опублікувати</button>
</form>
Що робить Livewire автоматично:
preventDefault()- модифікатор.preventне потрібен, на відміну від інших подій;- блокує форму на час запиту: кнопки відправки стають неактивними, поля - доступними лише для читання. Подвійне натискання не створить два пости;
- значення з
wire:modelпотрапляють на сервер разом із запитом на відправку - окремі запити на кожне поле не потрібні.
Параметри методу можна передати прямо в директиві: wire:submit="save('draft')".
Чому type="submit" важливий: кнопка без типу всередині форми теж відправляє її, а кнопка «Скасувати» з wire:click усередині форми має бути type="button", інакше натискання на неї відправить форму.
Enter у полі відправляє форму, як у звичайному HTML, - це правильна поведінка для доступності, і її не варто ламати.
Порівняно з контролером: логіка така сама - валідація, авторизація, збереження, редирект, - але без окремого маршруту й з помилками валідації, що з'являються без перезавантаження сторінки. Метод save() - публічна дія, яку можна викликати з браузера будь-коли, тож перевірки прав мають бути всередині нього.
Валідація в Livewire побудована на звичайній валідації Laravel - ті самі правила й повідомлення.
Спосіб 1 - $this->validate() з правилами:
public function save(): void
{
$validated = $this->validate([
'title' => 'required|min:3|max:255',
'email' => 'required|email',
]);
Post::create($validated);
}
Якщо дані невалідні, метод кидає виняток, виконання дії зупиняється, а помилки повертаються в браузер. validate() повертає лише перевірені поля - їх безпечно передавати в create().
Спосіб 2 - атрибут #[Validate] над властивістю:
use Livewire\Attributes\Validate;
new class extends Component {
#[Validate('required|min:3')]
public string $title = '';
#[Validate('required|email', as: 'електронна адреса')]
public string $email = '';
public function save(): void
{
$this->validate(); // правила беруться з атрибутів
}
};
Правила поруч із властивістю, і поле перевіряється автоматично при кожному оновленні з браузера (якщо воно надсилається на сервер - з .live).
Показ помилок - звичайні директиви Blade:
<input wire:model="title" @error('title') aria-invalid="true" @enderror>
@error('title')
<p class="text-sm text-red-600">{{ $message }}</p>
@enderror
Важливо: $this->validate() перед збереженням потрібен навіть з #[Validate] - атрибут перевіряє лише поля, які оновлювалися. Поле, якого користувач не торкався, без явного validate() залишиться неперевіреним.
Обмеження атрибутів: у PHP-атрибутах не можна використовувати об'єкти правил (Rule::unique(...), Password::defaults()). Для них - метод rules().
Українські повідомлення - через файли перекладів lang/uk/validation.php, як і в усьому Laravel; назву поля задає параметр as: чи метод validationAttributes().
Ручне керування помилками: $this->addError('email', 'Ця адреса вже зайнята'), $this->resetValidation('email').
Livewire відстежує, чи відрізняється стан у браузері від стану на сервері. Форма, в якій користувач щось змінив, але це ще не відправлено, вважається «брудною» (dirty).
wire:dirty показує елемент лише тоді, коли є незбережені зміни:
<form wire:submit="update">
<input wire:model="title">
<textarea wire:model="content"></textarea>
<div wire:dirty class="text-amber-600">Є незбережені зміни</div>
<button type="submit">Зберегти</button>
</form>
Щойно користувач почав вводити - повідомлення з'являється; після відправки форми й синхронізації з сервером - зникає.
Варіанти:
wire:dirty.remove- навпаки: показувати елемент, доки змін немає («Усе збережено»);wire:dirty.class="border-amber-500"- додати клас замість показу елемента;wire:target="title"- стежити лише за конкретною властивістю:
<input wire:model.live.blur="title" wire:dirty.class="border-amber-500" wire:target="title">
$dirtyу виразах і$wire.$dirty('email')в Alpine - перевірити програмно:
<button x-bind:disabled="! $wire.$dirty()">Зберегти</button>
Кнопка «Зберегти» активна лише тоді, коли є що зберігати.
Що важливо розуміти: «чисто» означає «синхронізовано із сервером», а не «збережено в базу». Якщо поле з wire:model.live уже відправило значення на сервер, а метод збереження ще не викликано, wire:dirty уже не покаже змін. Для форм, де важливо саме збереження в базу, стан «змінено» краще вести явно (наприклад, порівнювати з початковими значеннями).
Корисне доповнення: попередження при спробі піти зі сторінки з незбереженими змінами - обробник beforeunload в Alpine, що перевіряє $wire.$dirty().
wire:confirm показує діалог підтвердження перед виконанням дії. Якщо користувач натиснув «Скасувати» чи Escape, запит на сервер не надсилається.
<button
type="button"
wire:click="delete({{ $post->id }})"
wire:confirm="Видалити цей пост? Дію не можна скасувати."
>
Видалити
</button>
Працює з будь-якою дією: wire:click, wire:submit тощо.
Підтвердження введенням тексту - для особливо руйнівних дій (видалення облікового запису чи проєкту). Модифікатор .prompt, очікуваний текст - після |:
<button
wire:click="deleteAccount"
wire:confirm.prompt="Щоб підтвердити, введіть ВИДАЛИТИ|ВИДАЛИТИ"
>
Видалити обліковий запис
</button>
Дія виконається лише якщо введено точно ВИДАЛИТИ (з урахуванням регістру).
Що варто знати:
- це стандартні діалоги браузера (
confirm()/prompt()): виглядають по-різному в різних браузерах і не стилізуються. Для фірмового вигляду роблять власне модальне вікно (Alpine чи готові компоненти на кшталт Flux), а дію викликають з кнопки всередині нього; - підтвердження - це UX, а не захист. Метод
delete()можна викликати напряму з консолі браузера ($wire.delete(5)), минаючи діалог. Перевірка прав і власності запису обов'язкова в самому методі:
public function delete(Post $post): void
{
$this->authorize('delete', $post);
$post->delete();
}
- для масових чи незворотних операцій варто подумати про «м'яке» видалення (
SoftDeletes) і можливість відновити - це надійніше за будь-які діалоги; - текст діалогу - звичайний рядок, тож його варто перекладати так само, як решту інтерфейсу (
wire:confirm="{{ __('...') }}").