Питання
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
121 питань
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 кешує маршрути, конфіг та події разом.
Згенерувати клас, що реалізує ValidationRule:
php artisan make:rule Uppercase
class Uppercase implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strtoupper($value) !== $value) {
$fail('Поле :attribute має бути у верхньому регістрі.');
}
}
}
$request->validate(['code' => [new Uppercase]]);
Для разових перевірок можна передати замикання прямо в правило: 'field' => [fn ($attr, $value, $fail) => ...].
Підключення оголошуються в config/database.php. Вибір конкретного:
DB::connection('reporting')->table('events')->get();
class AnalyticsEvent extends Model
{
protected $connection = 'reporting'; // модель завжди на цьому з'єднанні
}
Типові сценарії:
- Read/Write splitting - окремі хости для читання й запису (Laravel сам маршрутизує
SELECTна репліку):'mysql' => ['read' => [...], 'write' => [...]], - Окрема аналітична або legacy-БД.
Laravel абстрагує файлові операції через фасад Storage поверх Flysystem. «Диски» (local, public, s3) налаштовуються в config/filesystems.php.
Storage::disk('s3')->put('reports/q1.pdf', $contents);
$url = Storage::disk('s3')->url('reports/q1.pdf');
$temporary = Storage::disk('s3')->temporaryUrl('reports/q1.pdf', now()->addMinutes(5));
php artisan storage:linkстворює символічне посиланняpublic/storage → storage/app/publicдля публічного доступу.- Зміна сховища (локально ↔ S3) не вимагає переписування коду - лише конфіг.
Livewire дозволяє будувати реактивні інтерфейси на PHP без написання JavaScript. Компонент має серверний стан (public-властивості) та дії (методи).
class Counter extends Component
{
public int $count = 0;
public function increment(): void { $this->count++; }
public function render() { return view('livewire.counter'); }
}
<button wire:click="increment">+</button>
<span>{{ $count }}</span>
При взаємодії Livewire робить AJAX-запит, повторно рендерить компонент на сервері й оновлює лише змінений DOM. Підходить для форм, таблиць, дашбордів у Laravel-моноліті; для складної клієнтської логіки доповнюється Alpine.js.
wire:model двостороннє прив'язує поле форми до public-властивості компонента.
<input type="text" wire:model="search"> {{-- deferred --}}
<input type="text" wire:model.live="search"> {{-- одразу при вводі --}}
<input type="text" wire:model.live.debounce.300ms="search">
<input type="text" wire:model.blur="search"> {{-- при втраті фокуса --}}
- За замовчуванням значення синхронізується deferred - на наступній дії (наприклад, submit), що економить запити.
.liveсинхронізує одразу - потрібно для живого пошуку чи валідації, але створює запит на кожне натискання (часто комбінують із.debounce).