Питання на співбесіді з Laravel
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
378 питань
Query Builder - плавний інтерфейс для побудови SQL-запитів без написання рядкового SQL. Працює з усіма підтримуваними СУБД і захищає від SQL-ін'єкцій через підготовлені вирази.
$users = DB::table('users')
->where('votes', '>', 100)
->orderBy('name')
->limit(10)
->get();
Повертає прості об'єкти stdClass. Eloquent побудований поверх Query Builder, тож ті самі методи (where, join, orderBy) доступні і на моделях.
Фасади надають зручний статичний інтерфейс до об'єктів із Service Container.
Cache::put('key', 'value', 60);
Route::get('/', fn () => view('home'));
Попри статичний синтаксис, це не справжні статичні методи: фасад через __callStatic() дістає реальний об'єкт із контейнера й викликає метод уже на ньому. Тому фасади тестовані - їх можна мокати:
Cache::shouldReceive('get')->once()->andReturn('value');
Кожен фасад має «accessor» - рядковий ключ сервісу в контейнері.
find($id)повертає модель за первинним ключем абоnull.findOrFail($id)повертає модель або кидаєModelNotFoundException, яку Laravel автоматично перетворює на HTTP 404.
$post = Post::find($id);
if (! $post) { abort(404); } // ручна перевірка
$post = Post::findOrFail($id); // те саме одним рядком
findOrFail робить контролери чистішими. Аналогічна пара для запитів - first() / firstOrFail().
Докладніше в документації: Eloquent: не знайдено / findOrFail
Це «upsert»-методи, що позбавляють від ручних перевірок «існує / не існує».
// знайти за email; якщо нема - створити з усіма атрибутами
User::firstOrCreate(
['email' => $email],
['name' => $name]
);
// знайти за email; оновити name; якщо нема - створити
User::updateOrCreate(
['email' => $email],
['name' => $name]
);
Перший масив - умови пошуку, другий - значення для створення/оновлення. Споріднений firstOrNew() повертає незбережений екземпляр.
Зовнішній ключ завжди лежить у таблиці тієї моделі, що належить іншій. Звідси й вибір методу.
// posts.user_id посилається на users.id
class User extends Model
{
public function posts(): HasMany
{
return $this->hasMany(Post::class); // ключ у чужій таблиці
}
}
class Post extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class); // ключ у своїй таблиці
}
}
Просте правило: у якій моделі колонка *_id, та модель і пише belongsTo. Інша сторона - hasOne чи hasMany.
Імена за замовчуванням:
hasManyшукає вpostsколонкуuser_id- ім'я моделі-власника в snake_case плюс_id;belongsToбере ім'я методу плюс_id: методauthor()шукатимеauthor_id, і тоді ключ доведеться передати явно, якщо колонка називається інакше.
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
Часта помилка новачків - написати hasOne там, де потрібен belongsTo, бо «в пості є один автор». Запит тоді шукатиме users.post_id, якого не існує.
Базовий layout оголошує «дірки» через @yield, дочірні шаблони їх заповнюють:
{{-- layouts/app.blade.php --}}
<html><body>
<main>@yield('content')</main>
</body></html>
{{-- posts/show.blade.php --}}
@extends('layouts.app')
@section('content')
<h1>{{ $post->title }}</h1>
@endsection
Навіщо: загальна розмітка (шапка, футер, підключення ассетів) описується один раз. Альтернатива - Blade-компоненти (<x-layout> зі слотами), які в нових проєктах часто витісняють @extends.
Blade-компоненти - багаторазові елементи UI, які підключаються тегом:
<x-alert type="error" :message="$error" />
Бувають:
- Анонімні - лише файл
resources/views/components/alert.blade.php. - Класові -
php artisan make:component Alert, з PHP-класом для логіки.
Дані передаються атрибутами, контент - через слоти:
{{-- components/card.blade.php --}}
<div class="card">
<h2>{{ $title }}</h2>
{{ $slot }}
</div>
Чистіше за @include, бо мають явний інтерфейс (props) та інкапсуляцію.
{{ $value }} екранує вивід через htmlspecialchars, а {!! $value !!} виводить як є.
{{ $user->name }} {{-- <b>Ада</b> покажеться як текст --}}
{!! $post->html !!} {{-- теги спрацюють --}}
Чому це важливо: якщо користувач запише в ім'я <script>...</script>, {{ }} покаже це як текст, а {!! !!} виконає скрипт у браузері кожного, хто відкриє сторінку. Це XSS - викрадення сесії, дії від імені жертви.
Правила:
- за замовчуванням - лише
{{ }}; {!! !!}- для HTML, який згенерував сам застосунок (Markdown, перетворений безпечним рендерером, чи HTML, очищений HTML Purifier);- вміст від користувача в
{!! !!}без очищення - ніколи.
Пастки, які екранування не закриває:
href="{{ $url }}"зjavascript:alert(1)- екранування не забороняє схему, такі посилання валідують (url:http,https);- дані в
<script>- для JavaScript берутьJs::from($data), а не{!! json_encode() !!}.
Для JavaScript-фреймворків, що теж використовують фігурні дужки, є @{{ name }} - Blade залишить вираз як є.
Іменований маршрут має псевдонім, за яким генерують URL - замість хардкоду шляху.
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
route('posts.show', $post); // у PHP
<a href="{{ route('posts.show', $post) }}">Деталі</a>
Навіщо: якщо URL зміниться (/posts → /articles), достатньо поправити маршрут - усі посилання оновляться автоматично. Також redirect()->route('posts.show', $post).
Розклад описується в PHP, а не в crontab - тож він лежить у git разом із кодом.
Опис завдання у routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('vacancies:parse')->twiceDaily(6, 18);
Schedule::command('reports:send')->dailyAt('09:00');
Schedule::job(new CleanupTempFiles)->hourly();
Що потрібно на сервері - один запис у cron:
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
Саме так: щохвилини й один рядок на весь застосунок. Cron будить Laravel щохвилини, а той сам вирішує, чиє зараз час. Новий пункт розкладу не потребує змін у cron.
Перевірити, не чекаючи часу:
php artisan schedule:list # що і коли має виконатися
php artisan schedule:run # виконати те, чому час зараз
php artisan schedule:work # тримати планувальник локально, без cron
Типові помилки новачка:
- Прописати кожне завдання окремим рядком у crontab - тоді розклад із коду не працює зовсім.
- Забути
cdу шлях проєкту: команда не знайде застосунок. - Запускати від імені іншого користувача, ніж веб-сервер, - права на
storageне збігаються, і завдання падає на записі логу. - Не дивитися на вивід: без
emailOutputOnFailure()чи логування падіння завдання непомітне.
Route::resource() одним рядком реєструє 7 маршрутів за RESTful-конвенцією:
Route::resource('posts', PostController::class);
| Метод | URL | Дія |
|---|---|---|
| GET | /posts | index |
| GET | /posts/create | create |
| POST | /posts | store |
| GET | /posts/{post} | show |
| GET | /posts/{post}/edit | edit |
| PUT/PATCH | /posts/{post} | update |
| DELETE | /posts/{post} | destroy |
apiResource()- те саме безcreate/edit(для API).->only([...])/->except([...])обмежують набір.
Файл приходить через $request->file(); зберігають через фасад Storage:
$request->validate([
'avatar' => ['required', 'image', 'max:2048'], // до 2 МБ
]);
$path = $request->file('avatar')->store('avatars', 'public');
// або з випадковим унікальним іменем - store() уже так робить
$user->update(['avatar_path' => $path]);
store()повертає шлях для збереження в БД.- Публічний URL -
Storage::url($path)(потребуєphp artisan storage:link). - Валідатори
image,mimes:pdf,docx,max:(в КБ) убезпечують від небажаних файлів.
Верифікація email підтверджує, що користувач справді володіє вказаною адресою. Laravel має це з коробки.
// 1. Модель реалізує контракт
class User extends Authenticatable implements MustVerifyEmail {}
// 2. Маршрути захищають middleware
Route::get('/dashboard', ...)->middleware(['auth', 'verified']);
Як працює: після реєстрації Laravel шле лист із підписаним URL (signed URL), перехід за яким ставить email_verified_at. Middleware verified не пускає непідтверджених користувачів. Подія Registered автоматично тригерить відправку листа.
Auth::attempt() перевіряє облікові дані й, якщо вони правильні, відкриває сесію.
public function login(LoginRequest $request): RedirectResponse
{
if (! Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
throw ValidationException::withMessages(['email' => __('auth.failed')]);
}
$request->session()->regenerate();
return redirect()->intended('/dashboard');
}
Покроково:
- Провайдер гарда шукає користувача за всіма полями, крім пароля.
- Пароль порівнюється з хешем через
Hash::check()- відкритий пароль ніде не зберігається й не порівнюється напряму. - При успіху ID користувача записується в сесію, і наступні запити бачать його через
auth()->user().
Що робиться після входу:
session()->regenerate()- новий ідентифікатор сесії, щоб підкинутий заздалегідь не дав зловмиснику увійти разом з жертвою (session fixation).redirect()->intended()- повертає туди, куди користувач ішов до перенаправлення на логін.
Додаткові умови передають тим самим масивом: Auth::attempt([...$credentials, 'is_active' => true]) не пустить вимкнений акаунт. У стартових наборах Laravel 13 усе це вже зроблено пакетом Fortify.
Звичайна сесія закінчується через SESSION_LIFETIME хвилин бездіяльності. «Запам'ятати мене» дозволяє лишатися в системі довше - без повторного входу.
Auth::attempt($credentials, remember: true);
Як це влаштовано: Laravel генерує випадковий токен, зберігає його в колонці users.remember_token і ставить довгоживучу зашифровану cookie. Коли сесія вже закінчилась, а cookie є, користувача автентифікують за нею, і Auth::viaRemember() повертає true.
Ризики й що з ними роблять:
- Вкрадена cookie = доступ на весь термін її дії. Тому cookie має
HttpOnlyіSecure(через HTTPS), а на чутливих діях пароль запитують ще раз - middlewarepassword.confirm. - Спільний комп'ютер. Галочка на бібліотечному ПК лишить акаунт відкритим для наступного. Тому вона не має бути ввімкнена за замовчуванням.
- Вихід скидає токен.
Auth::logout()оновлюєremember_token, і стара cookie перестає працювати - тож «вийти» справді виходить.
Якщо viaRemember() повертає true, розумно обмежити найнебезпечніші дії, доки людина не підтвердить пароль.
Питання з реальних технічних співбесід - 378 питань у 43 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
- Теми
- Eloquent 31 Архітектура 19 Тестування 14 Черги 14 Продуктивність 12 Безпека 11 Автентифікація 10 Бази даних 10
Готуєтесь до співбесіди не просто так: зараз на сайті 147 відкритих вакансій Laravel і PHP. Переглянути вакансії