Питання на співбесіді з Laravel
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
378 питань
abort() кидає HttpException з указаним кодом - обробник винятків перетворює його на відповідь з цим статусом:
abort(404);
abort(403, 'Цей проєкт архівовано.');
abort_if(! $user->isAdmin(), 403);
abort_unless($post->isPublished(), 404);
Після abort() код далі не виконується - це виняток, а не return.
Коли що використовувати:
- 404 - ресурсу немає чи користувачу не можна знати, що він існує;
- 403 - ресурс є, але доступ заборонено. Для перевірки прав краще політики (
$this->authorize(),Gate) - вони кидають той самий 403, але логіка прав зібрана в одному місці; findOrFail()замість ручногоabort(404)післяfind()- коротше й без забутої перевірки.
Власні сторінки помилок - просто Blade-шаблони за кодом статусу:
resources/views/errors/404.blade.php
resources/views/errors/403.blade.php
resources/views/errors/500.blade.php
resources/views/errors/503.blade.php # режим обслуговування
Усередині доступний виняток:
<h1>{{ $exception->getMessage() ?: 'Сторінку не знайдено' }}</h1>
Запасні сторінки для групи кодів: 4xx.blade.php і 5xx.blade.php використовуються, якщо немає шаблону для конкретного коду.
Стандартні шаблони Laravel можна скопіювати як основу:
php artisan vendor:publish --tag=laravel-errors
Типові пастки:
- сторінка 500 не повинна залежати від того, що могло зламатися: запитів до бази, сесії, складних компонентів. Якщо помилку спричинила база, сторінка помилки, що звертається до бази, впаде теж;
- повідомлення в
abort(403, '...')бачить користувач - жодних технічних деталей; - для API шаблони не використовуються: при
Accept: application/jsonLaravel повертає{"message": "..."}з відповідним статусом.
Докладніше в документації: Помилки: власні сторінки HTTP-помилок
Laravel використовує Monolog, а писати в лог можна через фасад Log чи хелпер logger():
use Illuminate\Support\Facades\Log;
Log::info('Замовлення оформлено', ['order_id' => $order->id]);
Log::warning('Платіжний шлюз відповідає повільно', ['ms' => $elapsed]);
Log::error('Не вдалося надіслати рахунок', ['order_id' => $order->id]);
logger('Коротке налагоджувальне повідомлення'); // рівень debug
Рівні логування (за RFC 5424, від найважливішого):
| Рівень | Коли використовувати |
|---|---|
emergency |
система непрацездатна |
alert |
потрібна негайна дія (база недоступна) |
critical |
критична помилка компонента |
error |
помилка, що не зупиняє застосунок, але потребує уваги |
warning |
щось незвичне, але не помилка (повторна спроба, повільна відповідь) |
notice |
нормальна, але важлива подія |
info |
звичайні події: вхід користувача, оформлення замовлення |
debug |
подробиці для налагодження |
Мінімальний рівень задається для каналу (змінна LOG_LEVEL). На продакшені зазвичай info чи warning: повідомлення debug просто відкидаються, не засмічуючи лог.
Другий аргумент - контекст: масив з даними, а не склеєний рядок.
// погано: неможливо шукати й фільтрувати
Log::info("Order {$order->id} paid by {$user->email}");
// добре: структуровані дані
Log::info('Order paid', ['order_id' => $order->id, 'user_id' => $user->id]);
Структуровані дані легко шукати в системах збору логів, і повідомлення лишається однаковим для всіх подій цього типу.
Куди пишеться: за замовчуванням у storage/logs/laravel.log (канал stack з single). Канали налаштовуються в config/logging.php.
Чого не писати в лог: паролі, токени, повні номери карток, персональні дані понад необхідне. Логи часто мають ширший доступ, ніж база даних, і зберігаються довше.
Не плутати з dd() і dump(): вони виводять у відповідь і для продакшену не підходять, а лог працює і в черзі, і в консольних командах, і без браузера.
Контролер чи замикання маршруту може повернути майже будь-що - Laravel сам перетворить результат на об'єкт Response:
| Що повертаємо | Що отримує клієнт |
|---|---|
| рядок | text/html з цим рядком |
| масив | JSON |
| модель Eloquent чи колекція | JSON (з урахуванням $hidden, $appends) |
view('posts.show', [...]) |
HTML з шаблону |
response(...) / response()->json(...) |
повний контроль: статус, заголовки, cookie |
redirect(...) |
відповідь 302 з заголовком Location |
| API Resource | JSON у форматі ресурсу |
об'єкт з інтерфейсом Responsable |
те, що поверне його toResponse() |
Route::get('/ping', fn () => 'pong');
Route::get('/settings', fn () => ['theme' => 'dark']);
Route::get('/users/{user}', fn (User $user) => $user);
return response('Created', 201)
->header('X-Request-Id', $requestId)
->cookie('seen_intro', '1', minutes: 60 * 24 * 30);
return response()->json(['message' => 'Accepted'], 202);
return response()->noContent(); // 204
Чому краще явні відповіді для API:
- модель напряму віддає всі її атрибути, крім
$hidden. Додали нову колонку в таблицю - вона автоматично з'явилася в API. API Resource описує формат явно; - статус-код: масив чи модель завжди повертають 200, а створення ресурсу - це 201, видалення - 204;
- заголовки (кешування, розташування нового ресурсу) задаються лише через об'єкт відповіді.
Відповідь має бути повернута: її треба саме повернути з контролера. Виклик response(...) без return нічого не робить. Щоб перервати виконання з будь-якого місця, використовують виняток: abort(404), throw ValidationException::withMessages(...) чи власний виняток з методом render().
Middleware бачить уже готову відповідь: після $next($request) можна додати заголовки чи cookie до будь-якої відповіді, незалежно від того, що повернув контролер.
Основні способи:
return redirect('/dashboard'); // за шляхом
return redirect()->route('posts.show', $post); // за іменем маршруту (модель підставить ключ)
return to_route('posts.show', ['post' => $post]); // коротша форма
return redirect()->action([PostController::class, 'index']);
return back(); // на попередню сторінку
return redirect()->away('https://partner.example.com'); // на зовнішній домен без перевірки URL
Разом з повідомленням - флеш-дані сесії, доступні лише в наступному запиті:
return redirect()->route('posts.index')
->with('status', 'Публікацію збережено.');
@if (session('status'))
<div class="alert">{{ session('status') }}</div>
@endif
Повернути з введеними даними й помилками (якщо перевіряєте вручну, а не через валідацію):
return back()
->withInput()
->withErrors(['email' => 'Цю адресу вже зареєстровано.']);
У формі введене повертається через old('email'), помилки - через @error('email'). Валідація Laravel робить це сама: при помилці вона кидає виняток, що перетворюється саме на такий редирект.
Патерн Post/Redirect/Get: після успішної обробки форми (POST) - завжди редирект, а не відображення сторінки. Інакше оновлення сторінки в браузері повторно відправить форму: другий платіж, дубль коментаря.
Типові помилки:
redirect()безreturn- нічого не відбувається, виконання триває;back()без запасного варіанта: якщо користувач прийшов за прямим посиланням, «назад» може вести на зовнішній сайт чи нікуди.back(fallback: route('home'));- редирект за URL з параметра запиту (
redirect($request->input('next'))) - відкритий редирект, яким користуються фішери. Для повернення після входу -redirect()->intended(...), а сторонні адреси - лише зі списку дозволених; away()для внутрішніх адрес - він не перевіряє URL взагалі; для своїх сторінок -route().
Коди: redirect() за замовчуванням дає 302. Постійне перенесення сторінки (SEO) - 301: redirect('/new', 301) чи Route::permanentRedirect('/old', '/new') у маршрутах.
Три методи для трьох ситуацій:
1. download - завантажити файл, що вже є на диску:
return response()->download(storage_path('app/reports/2026-q3.pdf'));
return response()->download($path, 'Звіт за III квартал.pdf'); // інша назва для користувача
return response()->download($path)->deleteFileAfterSend(); // тимчасовий файл
Заголовок Content-Disposition: attachment - браузер пропонує зберегти файл.
2. file - показати файл у браузері (зображення, PDF):
return response()->file(storage_path('app/invoices/42.pdf'));
Content-Disposition: inline - PDF відкриється у вбудованому переглядачі.
3. streamDownload - файл, якого немає на диску, генерується на льоту:
return response()->streamDownload(function () {
$out = fopen('php://output', 'w');
fputcsv($out, ['id', 'email']);
User::query()->lazyById(1000)->each(function (User $user) use ($out) {
fputcsv($out, [$user->id, $user->email]);
});
fclose($out);
}, 'users.csv', ['Content-Type' => 'text/csv']);
Дані віддаються частинами - експорт мільйона рядків не займає мільйона рядків пам'яті й не створює тимчасових файлів.
З дисків Storage (зокрема S3):
return Storage::download('reports/2026-q3.pdf', 'report.pdf');
return Storage::disk('s3')->response('avatars/42.jpg'); // показати
Безпека - головне при віддачі файлів:
- шлях ніколи не будується з введення користувача напряму:
download(storage_path('app/' . $request->file))дозволить завантажити../../.env(обхід шляху). Файл шукають за ідентифікатором у базі, де записано шлях; - перевірка прав перед віддачею - політика чи
Gate, як для будь-якого ресурсу; - приватні файли не кладуть у
public/чи публічний диск - вони мають віддаватися лише через контролер з перевіркою; - назва файлу від користувача - через
download($path, $safeName), Laravel сам коректно закодує не-ASCII символи в заголовку.
Для великих файлів на S3 краще не проганяти їх через PHP взагалі: тимчасове підписане посилання Storage::temporaryUrl() - і браузер завантажує прямо зі сховища.
Коли модель повертається з контролера чи перетворюється через toArray() / toJson(), у результат потрапляють усі атрибути з бази, а також завантажені зв'язки.
$hidden - атрибути, яких не має бути в масиві чи JSON:
class User extends Model
{
protected $hidden = ['password', 'remember_token', 'two_factor_secret'];
}
У Laravel 13 те саме можна записати атрибутом класу:
#[Hidden(['password', 'remember_token'])]
class User extends Model {}
$visible - навпаки, білий список: лише ці атрибути. Безпечніше, бо нова колонка в таблиці не з'явиться у відповіді автоматично.
$appends - додати обчислене значення з аксесора:
protected $appends = ['is_admin'];
protected function isAdmin(): Attribute
{
return Attribute::get(fn (): bool => $this->role === 'admin');
}
Без $appends аксесор працює в коді ($user->is_admin), але в JSON не потрапляє.
Тимчасово змінити видимість:
$user->makeVisible('email')->toArray();
$user->makeHidden('phone')->toArray();
$users->append('is_admin'); // для колекції
$user->setVisible(['id', 'name']);
Зв'язки: завантажені зв'язки серіалізуються разом з моделлю (ключ у snake_case). Щоб приховати зв'язок, його назву додають у $hidden.
Дати серіалізуються в ISO 8601 з UTC (2026-10-04T09:00:00.000000Z). Формат змінюють перевизначенням serializeDate() чи в касті: 'published_at' => 'datetime:Y-m-d'.
Підводні камені:
$appendsз аксесором, що звертається до зв'язку, дає N+1 при серіалізації колекції - кожна модель завантажує зв'язок окремо;$hidden- не захист: у коді атрибут доступний, і його можна вивести в шаблоні чи лог;- для API надійніше API Resources: формат описано явно, і він не залежить від того, які колонки є в таблиці.
Докладніше в документації: Серіалізація: приховування атрибутів
Laravel має два способи працювати з рядками, і обидва побудовані на тому самому наборі методів.
1. Статичний клас Str - кожен метод приймає рядок і повертає новий рядок:
use Illuminate\Support\Str;
Str::limit('Довгий текст статті', 10); // 'Довгий тек...'
Str::headline('laravel_framework'); // 'Laravel Framework'
Str::mask('olena@example.com', '*', 3); // 'ole**************'
Str::contains($title, ['Laravel', 'PHP']);
Str::uuid();
2. Fluent-рядки - Str::of() чи коротший хелпер str() повертають об'єкт Stringable, і методи можна складати в ланцюжок:
$slug = str(' Нова версія Laravel ')
->squish() // прибрати зайві пробіли
->limit(50, '')
->slug('-', 'uk');
Str::of($path)->afterLast('/')->beforeLast('.');
Між Str::of('...') і str('...') різниці немає: це той самий Stringable. А виклик str() без аргументів повертає об'єкт, що перенаправляє виклики до статичних методів Str: str()->uuid().
Коли що обирати:
| Ситуація | Що зручніше |
|---|---|
| одна операція | Str::limit($text, 100) |
| кілька перетворень поспіль | str($text)->squish()->lower()->... |
| умовні кроки | str($x)->when($cond, fn ($s) => $s->append('...')) |
Stringable - не рядок. Він приводиться до рядка в echo, Blade і конкатенації, але === з рядком дасть false, а функція з типом string у строгому режимі отримає об'єкт. Тому в кінці ланцюжка часто викликають ->toString() чи ->value():
$name = str($input)->trim()->title()->toString();
Кирилиця: методи Str враховують багатобайтові символи - str('Привіт')->length() повертає 6, а не 12, як strlen(). Але Str::slug() і Str::ascii() транслітерують за правилами мови, яку треба вказати явно, якщо результат має бути українською транслітерацією.
Перед тим як писати власну функцію для рядків варто переглянути список методів Str: там уже є between, excerpt, initials, wordCount, plural, squish, isUuid, isJson і десятки інших.
Клас Illuminate\Support\Number - обгортка над розширенням intl для форматування чисел з урахуванням локалі. Для більшості методів розширення intl обов'язкове.
use Illuminate\Support\Number;
Number::format(1234567.891, precision: 2, locale: 'uk'); // '1 234 567,89'
Number::currency(1500, in: 'UAH', locale: 'uk'); // '1 500,00 ₴'
Number::percentage(12.345, precision: 1); // '12.3%'
Number::fileSize(1536000, precision: 1); // '1.5 MB'
Number::abbreviate(1500000, precision: 1); // '1.5M'
Number::forHumans(1500000); // '2 million'
Number::spell(42, locale: 'uk'); // 'сорок два'
Number::ordinal(3); // '3rd'
Number::clamp($value, min: 1, max: 100);
Що варто знати:
precisionза замовчуванням 0 уfileSize,abbreviateіforHumans:Number::forHumans(1500000)округлює до'2 million'. Для дробової частини вказуйтеprecision;fileSizeрахує в степенях 1024, а не 1000;forHumansіabbreviateне локалізовані: вони завжди пишуть англійською (million,M);format,currency,percentage,spellприймаютьlocale, а в українській локалі роздільник тисяч - нерозривний пробіл, десятковий - кома.
Локаль для всього застосунку чи фрагмента:
// у сервіс-провайдері
Number::useLocale('uk');
Number::useCurrency('UAH');
// тимчасово для одного блоку
Number::withLocale('de', fn () => Number::format(1234.5)); // '1.234,5'
Зворотна операція - розібрати введене користувачем число з урахуванням локалі:
Number::parseFloat('1 234,5', locale: 'uk');
Гроші: Number::currency() - лише для відображення. Рахувати гроші через float не можна: зберігайте суму в копійках цілим числом (або в decimal у базі) і форматуйте тільки при виводі:
Number::currency($order->total_cents / 100, in: 'UAH');
У Blade це зручно загорнути в accessor моделі чи Blade-компонент, щоб формат валюти й точність були однаковими на всьому сайті.
Вкладені масиви (конфігурація, JSON зі стороннього API, payload вебхука) зручно читати крапковою нотацією замість ланцюжка isset:
use Illuminate\Support\Arr;
$data = [
'user' => ['profile' => ['name' => 'Olena']],
'orders' => [['total' => 10], ['total' => 20]],
];
Arr::get($data, 'user.profile.name'); // 'Olena'
Arr::get($data, 'user.email', 'n/a'); // 'n/a' - значення за замовчуванням
Arr::has($data, 'user.profile.name'); // true
data_get - те саме, але для масивів і об'єктів разом і з підтримкою *:
data_get($data, 'orders.*.total'); // [10, 20]
data_get($request->json(), 'payment.status');
data_get($model, 'author.profile.city'); // працює й з Eloquent-моделями
Запис:
Arr::set($config, 'mail.from.address', 'hello@example.com'); // за посиланням
data_set($data, 'orders.*.paid', false); // усім елементам
data_fill($data, 'meta.source', 'api'); // лише якщо ключа ще немає
Arr::forget($data, 'user.password');
Інші корисні методи:
| Метод | Що робить |
|---|---|
Arr::only($a, ['name', 'email']) |
лише вказані ключі |
Arr::except($a, ['password']) |
усі, крім вказаних |
Arr::wrap($value) |
гарантує масив: null → [], 'a' → ['a'] |
Arr::dot($a) / Arr::undot($a) |
вкладений масив ↔ плаский з ключами a.b.c |
Arr::pluck($a, 'user.name') |
значення поля з кожного елемента |
Arr::first($a, fn ($v) => ...) |
перший елемент за умовою |
Arr::string($a, 'key'), Arr::integer(...) |
значення з перевіркою типу, інакше виняток |
Пастка крапкової нотації: якщо в ключі масиву справді є крапка ('version.major' => 1), Arr::get($a, 'version.major') спершу перевіряє точний ключ і знайде його, але data_get і запис через Arr::set трактуватимуть крапку як вкладеність. Для таких даних краще звичайний доступ $a['version.major'].
Arr::wrap особливо корисний для параметрів, що приймають «одне значення або масив»: foreach (Arr::wrap($recipients) as $email) - без перевірок типу.
Для колекцій є ті самі можливості: collect($data)->get(...), ->pluck('user.name'), ->dot(). Якщо дані обробляються ланцюжком перетворень - колекція зручніша, для одного звернення - Arr чи data_get.
Laravel-пакет - це звичайний Composer-пакет, який інтегрується з фреймворком через сервіс-провайдер. Мінімальна структура:
acme/laravel-invoices/
├── composer.json
├── config/invoices.php
├── src/
│ ├── InvoicesServiceProvider.php
│ ├── InvoiceGenerator.php
│ └── Facades/Invoices.php
├── database/migrations/
├── resources/views/
└── tests/
Сервіс-провайдер - точка входу: реєструє класи в контейнері (register) і підключає конфігурацію, маршрути, представлення, міграції, команди (boot).
class InvoicesServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/invoices.php', 'invoices');
$this->app->singleton(InvoiceGenerator::class);
}
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'invoices');
}
}
Автоматичне виявлення (package discovery): провайдер і фасади описуються в composer.json пакета:
{
"name": "acme/laravel-invoices",
"autoload": {
"psr-4": { "Acme\\Invoices\\": "src/" }
},
"extra": {
"laravel": {
"providers": ["Acme\\Invoices\\InvoicesServiceProvider"],
"aliases": { "Invoices": "Acme\\Invoices\\Facades\\Invoices" }
}
}
}
Після composer require скрипт php artisan package:discover (Laravel запускає його в post-autoload-dump) читає секції extra.laravel усіх встановлених пакетів і записує список у bootstrap/cache/packages.php. Користувачу не треба нічого додавати в bootstrap/providers.php.
Як вимкнути виявлення - у composer.json застосунку:
"extra": {
"laravel": {
"dont-discover": ["barryvdh/laravel-debugbar"]
}
}
Наприклад, щоб підключати налагоджувальний пакет лише в локальному середовищі через власний провайдер.
Якщо пакет «не підхопився»: перевірити bootstrap/cache/packages.php, виконати php artisan package:discover і переконатися, що composer install не запускався з --no-scripts (тоді discover не виконується).
Залежності пакета: вимагати лише ті компоненти, які використовуються (illuminate/support, illuminate/database), а не весь laravel/framework - так пакет легше встановити й у застосунки на компонентах Laravel.
Коли пакет пишеться для конкретного застосунку (або виділяється з нього), незручно щоразу публікувати версію, щоб перевірити зміну. Path-репозиторій підключає пакет з локального каталогу.
~/code/
├── shop/ # застосунок
└── packages/
└── laravel-invoices/ # пакет
У composer.json застосунку:
{
"repositories": [
{
"type": "path",
"url": "../packages/laravel-invoices",
"options": { "symlink": true }
}
],
"require": {
"acme/laravel-invoices": "@dev"
}
}
composer update acme/laravel-invoices
Composer створює символьне посилання vendor/acme/laravel-invoices → ../packages/laravel-invoices. Зміни в коді пакета видно в застосунку одразу, без повторного встановлення.
Що варто знати:
- версія:
@devбере поточний стан каталогу. Якщо в пакеті є Git-теги чи полеversion, можна вимагати й звичайне обмеження (^1.0); - автозавантаження: після додавання нових класів у пакет зазвичай достатньо symlink, але при зміні секції
autoloadуcomposer.jsonпакета -composer dump-autoloadу застосунку; - package discovery спрацьовує як для звичайного пакета;
- у Docker symlink на каталог поза проєктом не працюватиме, якщо цей каталог не змонтовано в контейнер. Часто пакет тримають усередині репозиторію (
packages/у корені) з"url": "packages/*"; symlink: false- копіювання замість посилання: стабільніше для CI чи образів, але зміни потребуютьcomposer update.
Не забути перед публікацією:
- прибрати path-репозиторій з
composer.jsonзастосунку (чи тримати його лише локально) і вимагати опубліковану версію; - перевірити, що пакет встановлюється «з нуля» без застосунку - тести через Orchestra Testbench саме це й перевіряють.
Альтернатива для приватних пакетів: vcs-репозиторій (Git URL), Private Packagist чи Satis - тоді пакет встановлюється з тегів, як публічний.
Стартовий шаблон: замість ручного створення структури можна взяти spatie/package-skeleton-laravel - там уже налаштовані Testbench, Pest, PHPStan, GitHub Actions і публікація конфігурації.
Laravel AI SDK (laravel/ai) - офіційний пакет для роботи з великими мовними моделями та іншими AI-сервісами через єдиний API: OpenAI, Anthropic, Gemini, Mistral, DeepSeek, xAI, Ollama та інші. Код застосунку не залежить від конкретного провайдера - його можна змінити в конфігурації.
composer require laravel/ai
Ключі провайдерів задаються в .env (OPENAI_API_KEY, ANTHROPIC_API_KEY...), налаштування - у config/ai.php.
Агент - окремий PHP-клас, що інкапсулює все потрібне для однієї задачі: інструкції (системний промпт), контекст розмови, інструменти й схему відповіді.
php artisan make:agent SupportAssistant
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
class SupportAssistant implements Agent
{
use Promptable;
public function instructions(): string
{
return 'Ти асистент підтримки інтернет-магазину. Відповідай коротко й лише про замовлення та доставку.';
}
}
Виклик:
$response = (new SupportAssistant)->prompt('Де моє замовлення №1042?');
return (string) $response;
SupportAssistant::make(...) створює агента через контейнер - з автоматичним впровадженням залежностей.
Налаштування через атрибути:
#[Provider(Lab::Anthropic)]
#[MaxTokens(1024)]
#[Temperature(0.2)]
#[Timeout(60)]
class SupportAssistant implements Agent
Що ще вміє SDK, крім текстових агентів:
- структурована відповідь за JSON-схемою;
- інструменти - функції застосунку, які модель може викликати;
- стримінг відповіді й обробка в черзі;
- збереження розмов у базі (
RemembersConversations); - ембединги для семантичного пошуку (
Str::of($text)->toEmbeddings(),whereVectorSimilarToу Query Builder); - генерація зображень, аудіо, транскрибування;
- резервні провайдери:
provider: [Lab::OpenAI, Lab::Anthropic]- якщо перший недоступний чи вичерпав ліміт.
Чому агент - клас, а не просто виклик API: інструкції, інструменти й налаштування зібрані в одному місці, агента можна підробити в тестах (SupportAssistant::fake()) і перевикористати в контролері, команді чи черзі.
MCP (Model Context Protocol) - відкритий протокол, через який AI-клієнт (Claude Code, Cursor, Codex, Copilot, Junie) отримує доступ до інструментів, ресурсів і промптів зовнішньої системи. Сервер MCP описує, що він уміє, а модель вирішує, коли цим скористатися.
Laravel Boost (laravel/boost) - пакет для розробки, що робить AI-асистента обізнаним саме про ваш застосунок і версії пакетів.
composer require laravel/boost --dev
php artisan boost:install
boost:install питає, якими агентами ви користуєтеся, і генерує для них файли:
- конфігурацію MCP (
.mcp.jsonтощо) - агент запускаєphp artisan boost:mcp; - настанови (guidelines) - файли на кшталт
CLAUDE.md,AGENTS.mdз правилами для вашої версії Laravel і встановлених пакетів (Livewire 4, Filament 5, Pest...); - навички (skills) - детальні інструкції, що завантажуються на вимогу під конкретну задачу.
Інструменти MCP-сервера Boost:
| Інструмент | Навіщо агенту |
|---|---|
| Application Info | версії PHP, Laravel, пакетів, список моделей |
| Database Schema / Query | структура таблиць і запити до бази |
| Search Docs | документація саме для встановлених версій пакетів |
| Last Error / Read Log Entries | останні помилки з логів |
| Browser Logs | помилки з консолі браузера |
| Get Absolute URL | правильна адреса сторінки застосунку |
Чому це важливо: модель навчена на коді різних версій. Без контексту вона охоче напише код для Livewire 2 чи старий синтаксис маршрутів. Boost дає їй документацію й правила для того, що реально встановлено, і можливість перевірити схему бази замість вгадування.
Власні правила проєкту:
.ai/guidelines/*.md(чи.blade.php) - додаткові настанови, що потрапляють у згенеровані файли;.ai/skills/{назва}/SKILL.md- власні навички.
Оновлення: php artisan boost:update - перегенерувати настанови й навички після оновлення пакетів (зручно додати в post-update-cmd Composer).
Безпека: Boost - залежність лише для розробки (--dev). Інструмент запитів до бази виконується з правами застосунку, тож підключати агента варто до локальної бази, а не до продакшену.
Прапорець функції (feature flag) - перемикач, який вмикає чи вимикає частину функціональності без деплою. Код нової можливості вже на продакшені, але бачать її лише ті, кому прапорець увімкнено.
Навіщо це потрібно:
- поступовий запуск: спершу 5% користувачів, потім 50%, потім усі;
- trunk-based розробка: незавершену функцію можна зливати в
main, бо вона прихована прапорцем; - швидкий «вимикач»: якщо нова функція ламає продакшен, її вимикають без відкату коду;
- A/B-тести й бета-доступ для окремих клієнтів.
Laravel Pennant - офіційний пакет для прапорців. Прапорець оголошують у сервіс-провайдері:
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;
Feature::define('new-checkout', fn (User $user) => match (true) {
$user->isInternal() => true,
$user->isHighTrafficCustomer() => false,
default => Lottery::odds(1 / 10),
});
Замикання отримує скоп - за замовчуванням автентифікованого користувача - і повертає результат.
Перевірка в коді й шаблонах:
if (Feature::active('new-checkout')) {
return $this->newCheckout($request);
}
@feature('new-checkout')
<x-checkout.new />
@else
<x-checkout.legacy />
@endfeature
Для маршрутів є middleware EnsureFeaturesAreActive, що повертає помилку, якщо прапорець вимкнено.
Важлива деталь: драйвер за замовчуванням - database. Pennant зберігає обчислене значення в таблиці features, тож користувач, якому одного разу «випало» true в лотереї, і далі бачитиме нову версію. Це робить досвід стабільним, але означає, що зміна визначення не впливає на вже збережені значення.
Чого не робити: не залишати прапорці назавжди. Кожен прапорець - це дві гілки коду, які треба підтримувати й тестувати. Після повного запуску прапорець і стара гілка видаляються.
Socialite реалізує протокол OAuth для входу через сторонніх провайдерів: Google, GitHub, Facebook, GitLab, LinkedIn, Slack, X та інших.
Як проходить вхід:
- користувач натискає «Увійти через GitHub» - застосунок перенаправляє його на GitHub;
- GitHub питає користувача, чи дозволити доступ;
- GitHub повертає користувача на ваш callback-маршрут з одноразовим кодом;
- Socialite обмінює код на токен доступу й отримує профіль користувача;
- застосунок знаходить чи створює користувача і входить від його імені.
Налаштування - ключі застосунку провайдера в config/services.php:
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => '/auth/github/callback',
],
Два маршрути:
use Laravel\Socialite\Socialite;
Route::get('/auth/github/redirect', fn () => Socialite::driver('github')->redirect());
Route::get('/auth/github/callback', function () {
$githubUser = Socialite::driver('github')->user();
$user = User::updateOrCreate(
['github_id' => $githubUser->getId()],
['name' => $githubUser->getName(), 'email' => $githubUser->getEmail()],
);
Auth::login($user, remember: true);
return redirect('/dashboard');
});
Що варто знати:
- шукати користувача за ID провайдера (
github_id), а не за email: email у профілі можна змінити, а ID стабільний; - email може бути відсутнім - деякі провайдери не повертають його без додаткового скопу або якщо користувач приховав пошту;
- скопи визначають, до чого застосунок отримає доступ:
->scopes(['read:user']). Просіть мінімум; - параметр
stateSocialite перевіряє сам - це захист від CSRF у процесі OAuth. Для API без сесії єstateless(), але тоді захист доведеться забезпечити інакше; - тести: Socialite можна підмінити фейком і не ходити до справжнього провайдера.
Для кількох провайдерів зручно зберігати зв'язки в окремій таблиці social_accounts (provider, provider_id, user_id), а не додавати колонку в users для кожного провайдера.
Докладніше в документації: Socialite: автентифікація та збереження
Питання з реальних технічних співбесід - 378 питань у 43 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
- Теми
- Eloquent 31 Архітектура 19 Тестування 14 Черги 14 Продуктивність 12 Безпека 11 Автентифікація 10 Бази даних 10
Готуєтесь до співбесіди не просто так: зараз на сайті 147 відкритих вакансій Laravel і PHP. Переглянути вакансії