Питання на співбесіді з Laravel
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
378 питань
Валідація перевіряє вхідні дані за набором правил перш ніж їх використати. Laravel має багату систему правил і кілька способів валідації.
1. Метод validate() прямо в контролері (найпростіше):
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users,email'],
'age' => ['nullable', 'integer', 'min:18'],
]);
Якщо перевірка не пройдена, Laravel автоматично:
- для веб-запитів - робить редирект назад зі старими даними та помилками в сесії (доступні через
$errorsу Blade); - для API (запит очікує JSON) - повертає відповідь 422 зі структурою
{ "message": ..., "errors": {...} }.
2. FormRequest - для складнішої логіки правила й авторизацію виносять в окремий клас:
php artisan make:request StorePostRequest
public function rules(): array
{
return ['title' => ['required', 'max:255']];
}
Це розвантажує контролер. Є десятки вбудованих правил (required, email, unique, exists, confirmed, date), власні правила та умовна валідація.
Коли валідація не проходить, Laravel перенаправляє користувача назад, кладе помилки в сесію й «спалахом» зберігає введені дані. У шаблоні їх лишається вивести.
<input name="email" value="{{ old('email') }}" @class(['is-invalid' => $errors->has('email')])>
@error('email')
<p class="error">{{ $message }}</p>
@enderror
Що тут працює:
$errorsдоступна в кожному шаблоні групиweb- її додає middlewareShareErrorsFromSession;@errorвиводить перше повідомлення для поля в змінній$message;old('email')повертає значення, яке користувач надіслав минулого разу. Другим аргументом передають значення за замовчуванням - наприклад,old('email', $user->email)у формі редагування.
Чого не повертають через old(): паролі. Laravel не зберігає в сесії поля password і password_confirmation.
Для JSON-запитів перенаправлення немає - повертається відповідь 422 з об'єктом errors, і показ помилок бере на себе фронтенд.
Три правила відповідають на різні питання: чи має поле бути, чи може воно бути порожнім, і чи перевіряти його взагалі.
$request->validate([
'title' => ['required', 'string'], // має бути й не порожнє
'publish_at' => ['nullable', 'date'], // може бути порожнім, інакше - дата
'nickname' => ['sometimes', 'string'], // перевіряється, лише якщо поле прийшло
]);
required- поле має бути присутнім і непорожнім (неnull, не порожній рядок, не порожній масив).nullable-nullдозволений, і решта правил для нього не виконуються.sometimes- якщо ключа в запиті немає, правила поля не запускаються взагалі.
Чому nullable потрібен так часто: глобальні middleware TrimStrings і ConvertEmptyStringsToNull перетворюють порожнє поле форми на null. Без nullable правило date вважатиме null недійсною датою, хоча поле необов'язкове.
Де sometimes доречний: часткові оновлення (PATCH), коли клієнт надсилає лише змінені поля. sometimes|required означає «якщо поле прийшло, воно не може бути порожнім».
Докладніше в документації: Зауваження про необов'язкові поля
Mass Assignment - це присвоєння групи атрибутів моделі з масиву (наприклад, Model::create($request->all())). Вразливість виникає, коли користувач підкидає неочікувані поля (скажімо, is_admin).
Захист - білий або чорний список у моделі:
protected $fillable = ['title', 'body']; // дозволено лише ці
// або
protected $guarded = ['id', 'is_admin']; // заборонено ці
Найкраща практика: не передавати $request->all(), а валідувати й передавати $request->validated().
Eloquent і конструктор запитів передають значення окремо від SQL - прив'язками параметрів (prepared statements). База отримує WHERE email = ? і значення поруч, тож введення користувача ніколи не стає частиною команди.
User::where('email', $request->email)->first(); // безпечно
Де захист перестає працювати:
- Сирі методи з підставленим значенням:
User::whereRaw("email = '{$request->email}'")->first(); // ін'єкція
User::whereRaw('email = ?', [$request->email])->first(); // безпечно
Те саме для selectRaw, orderByRaw, DB::statement(), DB::raw().
- Назви колонок з введення. PDO не прив'язує назви колонок:
Post::orderBy($request->input('sort'))->get(); // небезпечно
Колонку беруть зі списку дозволених:
$sort = in_array($request->sort, ['title', 'created_at'], true) ? $request->sort : 'created_at';
- Ключі масиву в
update()з$request->all()- це вже масове присвоєння, від якого захищають$fillableіvalidated().
Правило: усе, що прийшло від користувача, - тільки значеннями через прив'язки, ніколи не частиною SQL.
Eloquent підтримує всі поширені типи зв'язків між таблицями, кожен оголошується методом на моделі:
- One To One -
hasOne/belongsTo. Приклад:User↔Profile. - One To Many -
hasMany/belongsTo. Приклад:Post→ багатоComment. - Many To Many -
belongsToManyчерез проміжну (pivot) таблицю. Приклад:User↔Role. - Has One/Many Through - доступ до віддаленого зв'язку через проміжну модель.
- Polymorphic -
morphTo/morphMany: модель належить кільком типам (наприклад,Commentможе належати іPost, іVideo).
Оголошення зв'язку:
class Post extends Model
{
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
class Comment extends Model
{
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
Використання:
$post->comments; // колекція коментарів
$comment->post->title; // зворотний бік
Post::with('comments')->get(); // eager loading проти N+1
Завжди завантажуйте потрібні зв'язки через with(), щоб уникнути проблеми N+1.
Soft Deletes - «м'яке» видалення: запис не стирається фізично, а отримує мітку часу в колонці deleted_at. Такі записи автоматично виключаються з усіх запитів.
class Post extends Model
{
use SoftDeletes; // + $table->softDeletes() у міграції
}
$post->delete(); // ставить deleted_at
Post::withTrashed()->get(); // включно з видаленими
$post->restore(); // відновити
$post->forceDelete(); // видалити назавжди
Навіщо: можливість відновлення, аудит, збереження посилальної цілісності.
Через об'єкт Request, який Laravel автоматично впроваджує в метод контролера:
public function store(Request $request)
{
$name = $request->input('name', 'default');
$email = $request->string('email'); // типізовані хелпери
$active = $request->boolean('active');
$all = $request->only(['name', 'email']);
}
Динамічний доступ $request->name теж працює, але його уникають через можливі колізії з параметрами маршруту. Для файлів - $request->file('avatar'), для перевірки наявності - $request->has('key').
Artisan - CLI Laravel. Він прискорює рутину: генерацію класів, міграції, очищення кешу, запуск черг тощо.
php artisan list # усі команди
php artisan make:model Post -mfsc # модель + міграція, фабрика, сідер, контролер
php artisan migrate
php artisan queue:work
php artisan tinker # інтерактивна REPL-консоль
Під капотом Artisan побудований на Symfony Console. Можна писати власні команди через make:command.
Сесії зберігають стан користувача між запитами. Laravel дає єдиний API поверх різних драйверів (file, cookie, database, redis), що налаштовуються в config/session.php.
session(['cart_id' => 42]); // записати
$id = session('cart_id'); // прочитати
$request->session()->forget('cart_id'); // видалити
session()->flush(); // очистити все
Для продакшену з кількома серверами зазвичай обирають redis або database, щоб сесія була спільною між інстансами.
Flash Data - дані, що живуть у сесії лише до наступного запиту й потім автоматично видаляються. Класичне застосування - повідомлення про результат дії після редиректу.
return redirect('/dashboard')->with('status', 'Профіль оновлено!');
@if (session('status'))
<div class="alert">{{ session('status') }}</div>
@endif
Під капотом - session()->flash('key', $value). Метод reflash() продовжує життя flash-даних ще на один запит.
Права описують у Gate або Policy, а перевіряють у трьох місцях: контролері, шаблоні й формі запиту.
У контролері - authorize(), який кидає 403 сам:
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// сюди дійде лише той, кому можна
}
Або через фасад, коли потрібна саме перевірка, а не зупинка:
if (Gate::allows('update', $post)) {
// ...
}
if ($request->user()->cannot('update', $post)) {
abort(403);
}
У Blade - директиви, які ховають те, чого не можна:
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Редагувати</a>
@endcan
@cannot('update', $post)
<span>Тільки перегляд</span>
@endcannot
У маршруті - middleware can:
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
Важливо: @can у шаблоні лише ховає кнопку. Це зручність для користувача, а не захист - без перевірки в контролері запит усе одно можна надіслати вручну. Перевірка на сервері обовʼязкова завжди, навіть коли кнопки не видно.
За замовчуванням гейти й політики для неавтентифікованого відвідувача повертають false - навіть не викликаючи метод. Це безпечний дефолт: забута перевірка не відкриє дію гостям.
Щоб метод викликався й для гостя, параметр користувача роблять необов'язковим:
class PostPolicy
{
public function view(?User $user, Post $post): bool
{
if ($post->is_published) {
return true; // опубліковане бачать усі
}
return $user?->id === $post->user_id; // чернетку - лише автор
}
}
Де це потрібно: публічний вміст із винятками - опубліковані статті для всіх, чернетки для автора; файли, частина яких доступна без входу.
Пастка: код усередині має пам'ятати про null - звернення $user->id без ?-> впаде на гостеві.
Для дій, які гостям не дозволені ніколи (редагування, видалення), параметр лишають обов'язковим - тоді Laravel відмовить ще до виклику методу.
У сучасному Laravel ассети збирає Vite. У шаблоні підключають директивою @vite:
@vite(['resources/css/app.css', 'resources/js/app.js'])
Під час розробки (npm run dev) Vite віддає файли з hot-reload; на продакшені (npm run build) - зібрані файли з хешами в імені для cache busting.
Для статичних файлів із public/ використовують хелпер asset('images/logo.png').
Вони перетворюють атрибути моделі «на льоту». У сучасному Laravel обидва описуються одним методом, що повертає Attribute:
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value), // accessor (читання)
set: fn (string $value) => strtolower($value), // mutator (запис)
);
}
- Accessor форматує значення при отриманні (
$user->name). - Mutator форматує значення перед збереженням у БД.
Корисно для форматування, нормалізації або роботи з Value Objects.
Питання з реальних технічних співбесід - 378 питань у 43 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
- Теми
- Eloquent 31 Архітектура 19 Тестування 14 Черги 14 Продуктивність 12 Безпека 11 Автентифікація 10 Бази даних 10
Готуєтесь до співбесіди не просто так: зараз на сайті 147 відкритих вакансій Laravel і PHP. Переглянути вакансії