Питання на співбесіді з Laravel
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
378 питань
MVC (Model-View-Controller) - це архітектурний патерн, який розділяє застосунок на три шари, аби логіка обробки даних, бізнес-правила та відображення не змішувались між собою. Laravel побудований навколо MVC, тож кожному шару відповідає своя директорія.
Три складові:
- Model - відповідає за дані та бізнес-логіку: спілкування з базою, зв'язки, валідаційні правила домену. У Laravel моделі розширюють Eloquent і лежать у
app/Models. - View - відповідає лише за відображення. У Laravel це Blade-шаблони у
resources/views; вони не повинні містити складної логіки. - Controller - приймає HTTP-запит, координує роботу моделей, готує дані й повертає View або відповідь. Лежать у
app/Http/Controllers.
Як це працює разом: запит потрапляє на маршрут (routes/web.php), той викликає метод контролера. Контролер звертається до моделі за даними й передає їх у view:
class PostController extends Controller
{
// Route Model Binding: $post резолвиться автоматично
public function show(Post $post)
{
// контролер координує: бере дані з моделі й віддає у view
return view('posts.show', ['post' => $post]);
}
}
Навіщо: розділення відповідальностей спрощує тестування, підтримку й командну роботу - верстку можна змінити, не чіпаючи запити до БД, і навпаки. Laravel розширює класичний MVC сервісним контейнером, middleware та провайдерами.
Міграція - це контроль версій для структури бази даних. Замість того щоб писати SQL вручну й узгоджувати його між розробниками, ви описуєте схему PHP-кодом, який комітиться в git і застосовується однаково в усіх середовищах.
Міграції зберігаються в директорії database/migrations. Кожен файл - це клас із двома методами:
up()- описує зміни, які треба застосувати (створити таблицю, додати колонку, індекс).down()- описує зворотну операцію для відкату (видалити таблицю чи колонку).
Створити міграцію:
php artisan make:migration create_posts_table
Згенерований файл заповнюють у методі up():
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
Основні команди:
php artisan migrate- застосувати нові міграції.php artisan migrate:rollback- відкотити останній «батч».php artisan migrate:fresh --seed- перестворити всі таблиці й засіяти даними.
Навіщо: будь-який розробник на новій машині отримує ідентичну схему БД однією командою, а зміни структури проходять через code review разом із кодом.
Маршрутизація визначає, як застосунок відповідає на запити: вона прив'язує URL та HTTP-метод до конкретної дії - замикання або методу контролера.
Маршрути оголошуються у файлах директорії routes:
web.php- веб-інтерфейс із сесіями, cookie та CSRF-захистом.api.php- stateless API (без сесій, з префіксом/api).console.php- команди та розклад.
Laravel підтримує методи для кожного HTTP-дієслова - Route::get, post, put, patch, delete, а також Route::match([...]) і Route::any:
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
// Групи: спільні middleware, префікс, простір імен
Route::middleware('auth')->prefix('admin')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
Ключові можливості:
{post}- параметр маршруту; його можна одразу резолвити в модель (Route Model Binding).->name('posts.show')- іменований маршрут для генерації URL черезroute('posts.show', $post).Route::resource(...)- одразу 7 RESTful-маршрутів для CRUD.
Переглянути всі зареєстровані маршрути: php artisan route:list.
Eloquent - це ORM (object-relational mapping) Laravel, реалізація патерну Active Record. Кожній таблиці відповідає модель (зазвичай у app/Models), рядок таблиці - це екземпляр моделі, а робота з даними виглядає як робота зі звичайними PHP-об'єктами замість написання SQL.
Створити модель (за конвенцією однина: Post → таблиця posts):
php artisan make:model Post -mf # одразу з міграцією та фабрикою
Базові операції (CRUD):
$post = Post::create(['title' => 'Привіт']); // create
$post = Post::find(1); // read
$post->update(['title' => 'Оновлено']); // update
$post->delete(); // delete
Post::where('is_published', true)->latest()->get();
Що дає Eloquent понад Query Builder:
- Зв'язки -
hasOne,hasMany,belongsTo,belongsToMany, поліморфні. - Аксесори/мутатори та касти атрибутів (наприклад, дати, enum, JSON).
- Події моделі та обзервери (
creating,saved,deleted). - Scopes для перевикористання умов запитів.
Eloquent побудований поверх Query Builder, тож ті самі методи (where, orderBy, join) доступні і на моделях.
Обидва методи виконують запит, але повертають різне:
get()повертає колекцію (Illuminate\Database\Eloquent\Collection) усіх відповідних моделей. Якщо нічого не знайдено - порожню колекцію, а неnull. Підходить, коли треба перебрати кілька записів.first()повертає одну першу модель абоnull, якщо нічого не знайдено. Підходить, коли очікуєш один запис.
$posts = Post::where('active', true)->get(); // Collection (0..N моделей)
$post = Post::where('slug', $slug)->first(); // Post|null
foreach ($posts as $post) { /* ... */ } // get() - ітеруємо
echo $post?->title; // first() - перевіряємо на null
Споріднені методи:
find($id)- пошук за первинним ключем.firstOrFail()/findOrFail()- якfirst()/find(), але кидаютьModelNotFoundException(HTTP 404), якщо запис відсутній.pluck('email')- колекція значень одного стовпця.value('email')- одне скалярне значення з першого рядка.
Підсумок: get() - багато рядків (колекція), first() - один рядок (модель або null).
Blade - це вбудований шаблонізатор Laravel. Шаблони мають розширення .blade.php, лежать у resources/views і компілюються у звичайний PHP-код, який кешується, - тож у рантаймі оверхеду майже немає. На відміну від чистого PHP, Blade дає лаконічний синтаксис і автоматичне екранування.
Основні можливості:
{{-- Вивід зі змінних (екранується автоматично) --}}
<h1>{{ $post->title }}</h1>
{{-- Директиви замість PHP-конструкцій --}}
@if ($user)
Вітаємо, {{ $user->name }}
@endif
@foreach ($posts as $post)
<li>{{ $post->title }}</li>
@endforeach
{{ $value }}- екранує вивід (захист від XSS);{!! $html !!}виводить «сирий» HTML без екранування.- Директиви:
@if,@foreach,@forelse,@auth,@can,@csrf,@vite. - Наслідування шаблонів через
@extends/@section/@yield- спільний layout без дублювання. - Компоненти
<x-alert />- багаторазові елементи UI зі слотами та props.
Навіщо: чистіший і безпечніший за вкладений PHP, з повторним використанням розмітки.
Файл .env лежить у корені проєкту й зберігає налаштування, специфічні для середовища, та секрети: доступи до БД, API-ключі, APP_KEY, режим APP_ENV. Ідея в тому, що той самий код працює в різних середовищах (локально, staging, продакшен) лише завдяки різним .env.
APP_ENV=local
APP_DEBUG=true
DB_CONNECTION=mysql
DB_PASSWORD=secret
STRIPE_KEY=sk_test_...
Ключові правила:
.envне комітиться в git (він у.gitignore) - кожен розробник і сервер має власний. Натомість комітять.env.exampleяк шаблон без секретів.- Значення зчитуються хелпером
env('KEY', 'default'), але викликатиenv()слід лише у файлахconfig/. - У застосунку звертайтесь через
config('services.stripe.key'), а неenv(...)напряму: післяphp artisan config:cache(оптимізація на проді) викликиenv()поза конфігом повертаютьnull.
Навіщо: секрети не потрапляють у код/репозиторій, а конфігурацію легко змінювати під середовище без редагування коду.
Створити файл у config/, що повертає масив, і читати значення через config() з «крапковою» нотацією.
// config/billing.php
return [
'currency' => env('BILLING_CURRENCY', 'UAH'),
'trial_days' => (int) env('BILLING_TRIAL_DAYS', 14),
'gateways' => ['liqpay', 'stripe'],
];
$currency = config('billing.currency');
$days = config('billing.trial_days', 14); // значення за замовчуванням
$days = Config::integer('billing.trial_days'); // типізоване читання
Правила:
env()- лише у файлахconfig/. Післяphp artisan config:cacheфайл.envбільше не читається, іenv()у коді повернеnull;- секрети (ключі API, паролі) - у
.env, у файлі конфігурації лише посилання на змінну; - нову змінну додають і в
.env.example, щоб колеги й CI знали про неї.
Типізовані методи Config::string(), integer(), boolean(), array() кидають виняток, якщо значення не того типу. Помилка конфігурації видно одразу, а не в дивній поведінці далі.
Конфігурацію сторонніх пакетів публікують командою php artisan config:publish чи vendor:publish.
CSRF (Cross-Site Request Forgery) - це атака, коли сторонній сайт змушує браузер автентифікованого користувача надіслати небажаний запит на ваш застосунок, використовуючи його активну сесію (наприклад, прихована форма, що переказує гроші).
Як Laravel захищає: для кожної активної сесії генерується унікальний CSRF-токен. Middleware ValidateCsrfToken перевіряє цей токен для всіх «небезпечних» методів - POST, PUT, PATCH, DELETE. Запити без валідного токена відхиляються з кодом 419.
У формах додають директиву @csrf, яка вставляє прихований інпут із токеном:
<form method="POST" action="/profile">
@csrf
<input name="email" type="email">
<button>Зберегти</button>
</form>
Для AJAX токен передають у заголовку X-CSRF-TOKEN (зазвичай із <meta name="csrf-token">):
fetch('/profile', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': token },
});
GET-запити токена не потребують (вони мають бути безпечними й не змінювати стан). Для stateless API на токенах (Sanctum) CSRF не застосовується.
Контролер - це клас, що групує логіку обробки запитів. Створюють його генератором Artisan; файл з'являється в app/Http/Controllers.
php artisan make:controller PostController # порожній
php artisan make:controller PostController --resource # 7 CRUD-методів
php artisan make:controller PostController --model=Post # з type-hint моделі
php artisan make:controller Api/PostController --api # без create/edit
php artisan make:controller PhotoController --invokable # один метод __invoke
Згенерований resource-контролер містить методи, що відповідають RESTful-конвенції:
class PostController extends Controller
{
public function index() {} // GET /posts
public function create() {} // GET /posts/create
public function store(Request $request) {} // POST /posts
public function show(Post $post) {} // GET /posts/{post}
public function edit(Post $post) {} // GET /posts/{post}/edit
public function update(Request $request, Post $post) {} // PUT/PATCH
public function destroy(Post $post) {} // DELETE
}
Прапорець --resource поєднується з Route::resource('posts', PostController::class), яка реєструє всі ці маршрути одним рядком.
Middleware - це шар фільтрації HTTP-запитів, що «обгортає» обробку: код може виконатися до того, як запит дійде до контролера, і/або після формування відповіді. Уявіть це як серію «застав», крізь які проходить кожен запит.
Створити middleware:
php artisan make:middleware EnsureUserIsActive
Логіка - у методі handle(); ключове - викликати $next($request), щоб передати запит далі конвеєром:
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->is_active) {
return redirect('login'); // перервати конвеєр
}
return $next($request); // пропустити далі
}
Реєстрація та призначення (у Laravel 11+ - у bootstrap/app.php), застосування до маршрутів:
Route::get('/dashboard', ...)->middleware('auth');
Route::middleware(['auth', 'verified'])->group(fn () => ...);
Типові вбудовані middleware: auth (автентифікація), throttle (обмеження частоти), verified (підтверджений email), signed (підписані URL). Middleware - основа для авторизації, логування, CORS, локалізації.
Клас створює команда:
php artisan make:middleware EnsureUserIsSubscribed
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
return redirect()->route('billing');
}
return $next($request);
}
Як підключити - залежно від того, де він потрібен:
// на маршруті чи групі
Route::get('/reports', ReportController::class)->middleware(EnsureUserIsSubscribed::class);
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(LogRequestId::class); // глобальний, на кожен запит
$middleware->web(append: [SetLocale::class]); // до групи web
$middleware->alias(['subscribed' => EnsureUserIsSubscribed::class]); // коротке ім'я
})
Після аліасу на маршруті можна писати ->middleware('subscribed').
Що змінилося: до Laravel 11 усе це жило в app/Http/Kernel.php (властивості $middleware, $middlewareGroups, $routeMiddleware). У нових застосунках цього класу немає - налаштування в bootstrap/app.php.
У Laravel 13 middleware можна призначити й атрибутом прямо на контролері: #[Middleware('subscribed')].
Обидва виводять значення в зручному вигляді через Symfony VarDumper:
dump($var)- друкує значення й продовжує виконання.dd($var)- «dump and die»: друкує й зупиняє виконання.
dump($user); // подивитись і йти далі
dd($request->all()); // подивитись і зупинитись
Споріднене: dump() для ланцюжків (->dump() на колекції/запиті), ray() (пакет), Log::debug() для логів замість виводу на екран.
Seeding - це процес наповнення бази даних початковими або тестовими даними. Laravel робить це через класи-сідери, які лежать у директорії database/seeders. Зручно для довідників (країни, ролі), демо-контенту та локальної розробки чи тестів.
Створити сідер:
php artisan make:seeder PostSeeder
Логіку вставки описують у методі run() - або через фабрики, або напряму через Query Builder:
class PostSeeder extends Seeder
{
public function run(): void
{
Post::factory()->count(50)->create(); // через фабрику
// або напряму
DB::table('roles')->insert([
['name' => 'admin'],
['name' => 'user'],
]);
}
}
Реєстрація: сідери викликають у DatabaseSeeder::run(), щоб запускати їх разом:
public function run(): void
{
$this->call([RoleSeeder::class, PostSeeder::class]);
}
Запуск:
php artisan db:seed- виконуєDatabaseSeeder.php artisan db:seed --class=PostSeeder- конкретний сідер.php artisan migrate:fresh --seed- перестворити БД і засіяти.
php artisan db:seed # DatabaseSeeder
php artisan db:seed --class=UserSeeder # один конкретний
php artisan migrate:fresh --seed # перебудувати базу й наповнити
DatabaseSeeder - точка входу. Він викликає інші сідери в потрібному порядку:
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
UserSeeder::class,
PostSeeder::class, // після користувачів - пости мають авторів
]);
}
}
Новий сідер створює php artisan make:seeder UserSeeder, і його треба додати в call(), інакше db:seed без --class його не запустить.
На продакшені команда питає підтвердження; у скриптах деплою додають --force. Але сідери з тестовими даними там не запускають узагалі - лише ідемпотентні довідники.
migrate:fresh видаляє всі таблиці - для локальної бази з даними, які шкода втратити, краще окремий db:seed --class=....
Питання з реальних технічних співбесід - 378 питань у 43 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
- Теми
- Eloquent 31 Архітектура 19 Тестування 14 Черги 14 Продуктивність 12 Безпека 11 Автентифікація 10 Бази даних 10
Готуєтесь до співбесіди не просто так: зараз на сайті 147 відкритих вакансій Laravel і PHP. Переглянути вакансії