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

Питання на співбесіді рівня Middle

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

40 питань

Транзакція гарантує атомарність: або всі операції виконуються, або жодна.

DB::transaction(function () use ($order) {
    $order->save();
    $order->items()->createMany($items);
    Inventory::decrement($order->product_id, $order->qty);
});

При винятку всередині замикання Laravel автоматично робить rollBack(). Ручний контроль:

DB::beginTransaction();
try {
    // ...
    DB::commit();
} catch (Throwable $e) {
    DB::rollBack();
    throw $e;
}

Другий аргумент transaction($cb, 3) задає кількість повторів при deadlock.

Докладніше в документації: Транзакції БД

Dependency Injection - патерн, за якого клас отримує залежності ззовні (зазвичай через конструктор), а не створює їх сам. Це знижує зв'язування й полегшує тестування (можна підсунути мок).

class OrderController
{
    public function __construct(
        private PaymentGateway $gateway, // інжектується контейнером
    ) {}
}

У Laravel DI працює «з коробки»: контейнер читає type-hints і автоматично будує граф залежностей. Інжектити можна й у методи контролера (method injection), зокрема сам Request.

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

Індекс - структура (зазвичай B-дерево), що пришвидшує пошук і сортування за стовпцем ціною уповільнення запису та додаткового місця.

$table->index('status'); // звичайний
$table->unique('email'); // унікальний
$table->index(['user_id', 'created_at']); // композитний

Правила:

  • Індексуйте стовпці у WHERE, JOIN, ORDER BY, зовнішні ключі.
  • Композитний індекс корисний за префіксом стовпців (порядок важливий).
  • EXPLAIN показує, чи використовується індекс.
  • Зайві індекси шкодять записам - балансуйте.

Докладніше в документації: Індекси в міграціях

Chunking обробляє великі набори даних порціями, щоб не тримати всі рядки в пам'яті одразу.

Post::chunk(200, function ($posts) {
    foreach ($posts as $post) { /* ... */ }
});
  • chunkById(200, ...) - безпечніший, коли під час обробки змінюються записи (нумерує за id, а не за offset).
  • lazy() / cursor() - повертають LazyCollection: ще менше пам'яті, але один активний запит.

Без chunking Post::all() на мільйонній таблиці впаде з браку пам'яті.

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

Rate Limiting обмежує кількість запитів за період. Іменовані обмежувачі визначають через RateLimiter::for() (у Laravel 11+ зазвичай у bootstrap/app.php або AppServiceProvider::boot()):

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

Застосування до маршрутів:

Route::middleware('throttle:api')->group(...);
Route::post('/login', ...)->middleware('throttle:5,1'); // 5 за хвилину

При перевищенні - HTTP 429 із заголовками Retry-After та X-RateLimit-*.

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

View Composer прив'язує дані до шаблону щоразу, коли той рендериться - щоб не дублювати передачу спільних даних у багатьох контролерах.

View::composer('partials.sidebar', function ($view) {
    $view->with('categories', Category::all());
});

Тепер будь-яке відображення partials.sidebar автоматично отримає $categories. Зручно для меню, лічильників, віджетів у layout. Реєструється у boot() сервіс-провайдера.

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

Це різні рівні автентифікації:

  • Breeze - стартовий набір UI: реєстрація/вхід/скидання пароля на Blade+Livewire або React/Vue. Для швидкого старту.
  • Fortify - headless-бекенд автентифікації (без UI): логіка реєстрації, 2FA, скидання пароля. Під ним працює Breeze/Jetstream.
  • Sanctum - легка автентифікація для SPA (через cookie) та простих API-токенів. Дефолт для більшості API.
  • Passport - повноцінний OAuth2-сервер: видача access/refresh токенів стороннім клієнтам. Обирають, коли потрібен саме OAuth2.

Правило: SPA чи мобільний застосунок → Sanctum; «увійти через наш сервіс» для третіх сторін → Passport.

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

Sanctum дає два режими:

1. API-токени - модель випускає токен, який клієнт шле в заголовку Authorization: Bearer ...:

$token = $user->createToken('mobile', ['posts:read'])->plainTextToken;

Route::middleware('auth:sanctum')->get('/user', fn (Request $r) => $r->user());

2. SPA-автентифікація - для односторінкових застосунків на тому ж домені використовує звичайні сесійні cookie + CSRF (без зберігання токенів у JS, що безпечніше).

Токени підтримують abilities (scopes): $user->tokenCan('posts:read'). Трейт HasApiTokens додає tokens()-зв'язок і createToken().

Докладніше в документації: Sanctum: API-токени

Notification - одне повідомлення, яке можна доставити кількома каналами одночасно.

class InvoicePaid extends Notification
{
    public function via(object $notifiable): array
    {
        return ['mail', 'database', 'broadcast'];
    }

    public function toMail($notifiable): MailMessage { /* ... */ }
    public function toArray($notifiable): array { /* для database */ }
}

$user->notify(new InvoicePaid($invoice));

Канали з коробки: mail, database (зберігає в notifications), broadcast (WebSockets), Vonage (SMS), Slack. Є community-канали (Telegram, push). Канал database зручний для «дзвіночка» сповіщень у UI; реалізувавши ShouldQueue, відправку виносять у чергу.

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

Repository Pattern абстрагує доступ до даних за інтерфейсом, ховаючи конкретну реалізацію (Eloquent, API, файли).

interface PostRepository
{
    public function published(): Collection;
}

Нюанс для Laravel: Eloquent уже є реалізацією Active Record і сам по собі є абстракцією над БД. Тому додавання репозиторіїв часто надлишкове й лише дублює API Eloquent. Виправдане, коли:

  • треба підміняти джерело даних (Eloquent ↔ зовнішнє API);
  • є складна доменна логіка (DDD), де моделі не мають знати про БД.

У типовому CRUD-застосунку спільнота вважає це антипатерном.

LazyCollection використовує PHP-генератори, щоб тримати в пам'яті лише один елемент за раз - критично для величезних наборів.

LazyCollection::make(function () {
    $handle = fopen('huge.csv', 'r');
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
})->filter(...)->take(100)->each(...);

З Eloquent:

User::cursor()->each(function ($user) { /* по одному рядку */ });

На відміну від звичайної Collection, методи не матеріалізують увесь набір - обчислення «ліниві» й виконуються лише при ітерації.

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

Custom Cast інкапсулює логіку перетворення атрибута між форматом БД та об'єктом PHP.

class Money implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): MoneyValue
    {
        return new MoneyValue($value); // з БД → Value Object
    }

    public function set($model, $key, $value, $attributes): array
    {
        return ['price' => $value->cents]; // VO → у БД
    }
}

protected $casts = ['price' => Money::class];

Застосування: робота з Value Objects, шифрування полів, JSON-структури. Вбудовані касти: array, encrypted, datetime, enum-класи, AsCollection.

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

Laravel має тестування з коробки поверх PHPUnit; популярна надбудова - Pest із лаконічним синтаксисом.

it('creates a post', function () {
    $user = User::factory()->create();

    $response = $this->actingAs($user)->post('/posts', [
        'title' => 'Hello',
    ]);

    $response->assertRedirect();
    $this->assertDatabaseHas('posts', ['title' => 'Hello']);
});
  • Feature-тести перевіряють HTTP-флоу (більшість тестів); Unit - окремі класи ізольовано.
  • Трейт RefreshDatabase ізолює тести, відкочуючи зміни після кожного; дані генерують фабрики.
  • actingAs($user) автентифікує користувача для захищених маршрутів.

Корисні асерції: assertStatus, assertSee, assertJson, assertDatabaseHas, assertRedirect, assertAuthenticated.

Мокінг - ізоляція від зовнішніх ефектів (API, листи, черги):

Mail::fake();         Mail::assertSent(OrderShipped::class);
Queue::fake();        Queue::assertPushed(ProcessPodcast::class);
Notification::fake(); Http::fake();

// мок сервісу через контейнер
$this->mock(PaymentGateway::class)
    ->shouldReceive('charge')->once()->andReturn(true);

Запуск: php artisan test, --filter=PostTest, --parallel (швидше).

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

Підзапит - запит, вкладений в інший. Eloquent дозволяє вставляти їх у select, where, orderBy.

// додати останню дату входу кожного користувача одним запитом
User::addSelect(['last_login_at' => Login::select('created_at')
    ->whereColumn('user_id', 'users.id')
    ->latest()
    ->limit(1),
])->get();

// сортування за підзапитом
Destination::orderByDesc(
    Flight::select('arrived_at')->whereColumn('destination_id', 'destinations.id')->latest()->limit(1)
)->get();

Підзапити допомагають уникнути N+1 і зайвих операцій JOIN, обчислюючи похідні значення в межах одного запиту.

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

php artisan route:cache компілює всі маршрути в один файл, тож фреймворк не реєструє їх заново на кожному запиті - це помітно пришвидшує бутстрап на застосунках із сотнями маршрутів.

php artisan route:cache    # на деплої
php artisan route:clear    # скинути

Лише для продакшену. Обмеження: маршрути на основі замикань кешувати не можна - потрібні контролери. Команда php artisan optimize кешує маршрути, конфіг та події разом.

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