Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Питання на співбесіді з 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 - її додає middleware ShareErrorsFromSession;
  • @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().

Докладніше в документації: Mass Assignment

Eloquent і конструктор запитів передають значення окремо від SQL - прив'язками параметрів (prepared statements). База отримує WHERE email = ? і значення поруч, тож введення користувача ніколи не стає частиною команди.

User::where('email', $request->email)->first();   // безпечно

Де захист перестає працювати:

  1. Сирі методи з підставленим значенням:
User::whereRaw("email = '{$request->email}'")->first();     // ін'єкція
User::whereRaw('email = ?', [$request->email])->first();    // безпечно

Те саме для selectRaw, orderByRaw, DB::statement(), DB::raw().

  1. Назви колонок з введення. PDO не прив'язує назви колонок:
Post::orderBy($request->input('sort'))->get();   // небезпечно

Колонку беруть зі списку дозволених:

$sort = in_array($request->sort, ['title', 'created_at'], true) ? $request->sort : 'created_at';
  1. Ключі масиву в 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.

Докладніше в документації: Зв’язки Eloquent

Soft Deletes - «м'яке» видалення: запис не стирається фізично, а отримує мітку часу в колонці deleted_at. Такі записи автоматично виключаються з усіх запитів.

class Post extends Model
{
    use SoftDeletes; // + $table->softDeletes() у міграції
}

$post->delete(); // ставить deleted_at
Post::withTrashed()->get(); // включно з видаленими
$post->restore(); // відновити
$post->forceDelete(); // видалити назавжди

Навіщо: можливість відновлення, аудит, збереження посилальної цілісності.

Докладніше в документації: Soft Deletes

Через об'єкт 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').

Докладніше в документації: HTTP-запити

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.

Докладніше в документації: Artisan Console

Сесії зберігають стан користувача між запитами. 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-даних ще на один запит.

Докладніше в документації: 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').

Докладніше в документації: Vite та ассети

Вони перетворюють атрибути моделі «на льоту». У сучасному 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.

Докладніше в документації: Accessors та Mutators

Питання з реальних технічних співбесід - 378 питань у 43 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.

Рівні
Junior 101 Middle 147 Senior 130

Готуєтесь до співбесіди не просто так: зараз на сайті 147 відкритих вакансій Laravel і PHP. Переглянути вакансії