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

Питання

Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів

121 питань

Filament - фреймворк Server-Driven UI для Laravel: інтерфейси (адмінки, панелі) описуються на PHP через структуровані об'єкти, а не верстку.

public static function form(Schema $schema): Schema
{
    return $schema->components([
        TextInput::make('title')->required(),
        Select::make('status')->options(Status::class),
    ]);
}
  • Побудований на Livewire, Alpine.js і Tailwind CSS.
  • Будівельні блоки: Resources, Forms, Tables, Actions, Infolists, Widgets.
  • Компоненти ініціалізуються статичними make()-методами; динамічні значення задаються замиканнями з утилітами Get/Set.
Query Builder Eloquent
Повертає stdClass / масиви моделі
Рівень близько до SQL ORM поверх QB
Зв'язки, події, касти ні так
Оверхед мінімальний невеликий
// Query Builder
DB::table('users')->where('active', 1)->get();

// Eloquent
User::where('active', 1)->get();

Eloquent виразніший і зручніший для бізнес-логіки. Для важких масових операцій (мільйони рядків, складні агрегати) інколи свідомо обирають чистий Query Builder заради швидкості та меншого споживання пам'яті.

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

У belongsToMany проміжна (pivot) таблиця зберігає зв'язки. Додаткові стовпці на ній оголошують через withPivot():

public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class)
        ->withPivot('assigned_at', 'is_primary')
        ->withTimestamps();
}

Доступ і керування:

$user->roles->first()->pivot->assigned_at;  // читання pivot-даних

$user->roles()->attach($roleId, ['is_primary' => true]); // додати
$user->roles()->detach($roleId); // прибрати
$user->roles()->sync([1, 2, 3]);// привести до набору
$user->roles()->updateExistingPivot($roleId, [...]); // оновити pivot

Для окремої моделі pivot використовують ->using(RoleUser::class).

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

Логування в Laravel побудоване на Monolog; канали налаштовуються в config/logging.php.

Log::info('Замовлення створено', ['id' => $order->id]);
Log::channel('slack')->critical('Платіж не пройшов');

Типи каналів: single (один файл), daily (ротація по днях), slack, papertrail, stderr, а також stack - який пише одразу в кілька:

'stack' => ['driver' => 'stack', 'channels' => ['daily', 'slack']],

Практики: рівні (debugemergency), структуроване логування з контекстом, маскування PII, окремий канал для критичних подій у Slack/Sentry. У проді LOG_LEVEL зазвичай warning+.

Докладніше в документації: Логування

Signed URL - посилання з криптографічним підписом у query-рядку. Якщо хтось змінить URL, підпис стане недійсним - підробка неможлива без APP_KEY.

// згенерувати
URL::signedRoute('unsubscribe', ['user' => $id]);
URL::temporarySignedRoute('download', now()->addMinutes(30), ['file' => $id]);
// перевірити (middleware або вручну)
Route::get('/download/{file}', ...)->middleware('signed');

Застосування: посилання-відписки в листах, тимчасові посилання на скачування, підтвердження email - там, де потрібен захищений доступ без автентифікації. temporarySignedRoute додає термін дії.

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

Оптимізація йде кількома шарами:

База даних

  • Усунення N+1 (eager loading with()), правильні індекси, аналіз через EXPLAIN.
  • Read/write репліки, кешування важких запитів.

Кешування

  • Cache::remember() для дорогих обчислень; повне кешування сторінок/фрагментів.
  • php artisan optimize (config/route/view/event cache), OPcache.

Фонова робота

  • Винесення повільних задач (email, обробка медіа, виклики API) у черги.

Інфраструктура

  • Laravel Octane (Swoole/FrankenPHP) тримає застосунок у пам'яті.
  • CDN для статики, горизонтальне масштабування за load balancer, спільні сесії/кеш у Redis.

Перед оптимізацією - профілювання (Telescope, Clockwork, Debugbar), щоб бити по реальних вузьких місцях.

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

Шлях запиту:

  1. public/index.php - єдина точка входу; підключає автозавантажувач Composer.
  2. Створюється екземпляр застосунку (Service Container) із bootstrap/app.php.
  3. HTTP Kernel обробляє запит, завантажує Service Providers (registerboot).
  4. Запит проходить глобальні middleware (наприклад, обробка сесій, CSRF).
  5. Router зіставляє URL із маршрутом, виконуються middleware маршруту.
  6. Викликається контролер/замикання, формується Response.
  7. Відповідь проходить middleware у зворотному порядку й повертається клієнту; виконується terminate().

Ключова ідея: контейнер і провайдери бутстрапять застосунок, а middleware утворюють «цибулю» навколо обробки запиту.

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

  • CQRS (Command Query Responsibility Segregation) розділяє запис (Commands, що змінюють стан) і читання (Queries). Read-модель можна оптимізувати окремо (денормалізовані проєкції, окрема БД).
  • Event Sourcing зберігає не поточний стан, а послідовність подій; поточний стан відновлюється їх відтворенням. Дає повний аудит і «подорож у часі».
// концептуально
$aggregate->retrieve($uuid)
    ->placeOrder($data) // emit OrderPlaced
    ->persist(); // зберегти подію

У Laravel зазвичай через пакет spatie/laravel-event-sourcing (aggregates, projectors, reactors). Застосовувати варто там, де критичні аудит і складна доменна логіка - це додає суттєву складність, тож не для типового CRUD.

Octane запускає застосунок через high-performance сервери (Swoole, FrankenPHP, RoadRunner): фреймворк бутстрапиться один раз і тримається в пам'яті, обслуговуючи наступні запити без повторної ініціалізації.

php artisan octane:start --server=frankenphp

Це усуває оверхед завантаження на кожному запиті й дає кратний приріст RPS.

Підводні камені: оскільки процес довготривалий, треба уникати витоків стану між запитами - статичні властивості, синглтони з накопиченим станом, глобальні змінні можуть «протікати» від запиту до запиту. Octane надає хуки flush і перезапускає воркери для безпеки.

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

PHP використовує підрахунок посилань + збирач циклічних посилань. У звичайному веб-запиті пам'ять звільняється наприкінці запиту, тож витоки малопомітні. Але у довготривалих процесах (черги, Octane) пам'ять накопичується.

Як уникати:

  • Не зберігати стан у статичних властивостях/синглтонах між задачами.
  • unset() великих структур, скидати накопичувачі (логи запитів DB::flushQueryLog()).
  • Обробляти дані порціями (chunk, lazy), не тримати все в пам'яті.
  • Перезапускати воркери за лімітом: queue:work --max-jobs=1000 --max-time=3600 або при досягненні --memory.

Octane має gc_collect_cycles()-хуки; Horizon автоматично перезапускає воркери, що «розпухли».

Pessimistic locking блокує рядок у БД до завершення транзакції - інші транзакції чекають.

DB::transaction(function () {
    $account = Account::lockForUpdate()->find($id); // блокування на запис
    $account->balance -= 100;
    $account->save();
});

sharedLock() - блокування на читання.

Optimistic locking не блокує, а перевіряє версію/updated_at перед записом; якщо хтось уже змінив рядок - оновлення відхиляється, операцію повторюють.

UPDATE accounts SET balance = ?, version = version + 1
WHERE id = ? AND version = ?
  • Pessimistic - для високої конкуренції за тими ж рядками (платежі, склад).
  • Optimistic - коли конфлікти рідкісні; масштабується краще, бо не тримає блокувань.

Докладніше в документації: Песимістичні блокування

Два основні підходи:

Single Database (shared schema) - усі орендарі в одній БД, розділення за tenant_id у кожній таблиці. Ізоляція забезпечується global scope, що автоматично додає where tenant_id = ?.

  • Плюси: просто й дешево. Мінуси: ризик витоку даних при помилці у scope.

Multi Database - окрема БД (або схема) на орендаря, динамічне перемикання з'єднання за поточним tenant.

  • Плюси: сильна ізоляція, легше бекапити/масштабувати окремого клієнта. Мінуси: складніші міграції (на кожну БД).
Tenancy::initialize($tenant); // перемкнути конфіг з'єднання/кеш/файли

Популярний пакет - stancl/tenancy. Вибір залежить від вимог до ізоляції та масштабу.

Гексагональна архітектура ізолює ядро бізнес-логіки від зовнішнього світу.

  • Ports - інтерфейси, через які ядро спілкується зі світом (PaymentGateway, UserRepository).
  • Adapters - конкретні реалізації портів (StripeAdapter, EloquentUserRepository, HTTP-контролер).
[ HTTP / CLI / Queue ]  →  Port  →  [ Domain Core ]  →  Port  →  [ DB / API / Mail ]
        (adapters)                   (бізнес-логіка)              (adapters)

Ядро не знає про Laravel, БД чи HTTP - воно залежить лише від абстракцій. Перевага: домен тестується ізольовано, зовнішні залежності легко підмінювати. У Laravel порти біндять до адаптерів через Service Container.

Horizon - панель і конфігурація черг на Redis. Дає те, чого немає в базовому queue:work.

// config/horizon.php
'supervisor-1' => [
    'connection' => 'redis',
    'queue' => ['high', 'default'],
    'balance' => 'auto', // авто-балансування воркерів
    'maxProcesses' => 10,
],

Можливості:

  • Реалтайм-метрики: throughput, час очікування, runtime задач.
  • Авто-балансування процесів між чергами за навантаженням.
  • Керування невдалими задачами, теги, сповіщення про довге очікування (LongWaitDetected).

Запуск - php artisan horizon; під капотом це менеджер довготривалих воркерів. Дашборд захищають gate viewHorizon.

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

  • Паролі - лише одностороннє хешування (Bcrypt/Argon2): Hash::make() / Hash::check(). Ніколи не шифрування й не власні алгоритми.
  • PII (двостороннє) - Crypt::encryptString() або каст encrypted на атрибуті моделі:
    protected $casts = ['ssn' => 'encrypted'];
    
  • Ключі та секрети - у .env / секретних сховищах (AWS Secrets Manager, Vault), не в git. Ротація APP_KEY потребує перешифрування.
  • Транзит - лише HTTPS/TLS.
  • Логи - маскувати PII; уникати dd() у проді.
  • Доступ - принцип найменших привілеїв, audit log (наприклад, spatie/laravel-activitylog).

Докладніше в документації: Шифрування