Питання на співбесіді рівня 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.
Індекс - структура (зазвичай 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() на мільйонній таблиці впаде з браку пам'яті.
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-*.
View Composer прив'язує дані до шаблону щоразу, коли той рендериться - щоб не дублювати передачу спільних даних у багатьох контролерах.
View::composer('partials.sidebar', function ($view) {
$view->with('categories', Category::all());
});
Тепер будь-яке відображення partials.sidebar автоматично отримає $categories. Зручно для меню, лічильників, віджетів у layout. Реєструється у boot() сервіс-провайдера.
Це різні рівні автентифікації:
- 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 дає два режими:
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().
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, відправку виносять у чергу.
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, методи не матеріалізують увесь набір - обчислення «ліниві» й виконуються лише при ітерації.
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.
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, обчислюючи похідні значення в межах одного запиту.
php artisan route:cache компілює всі маршрути в один файл, тож фреймворк не реєструє їх заново на кожному запиті - це помітно пришвидшує бутстрап на застосунках із сотнями маршрутів.
php artisan route:cache # на деплої
php artisan route:clear # скинути
Лише для продакшену. Обмеження: маршрути на основі замикань кешувати не можна - потрібні контролери. Команда php artisan optimize кешує маршрути, конфіг та події разом.