Питання
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
121 питань
2FA вимагає двох факторів: «що знаю» (пароль) + «що маю» (код із застосунку/SMS). Найпоширеніше - TOTP (Time-based One-Time Password) сумісно з Google Authenticator.
У Laravel найпростіше через Fortify, який має 2FA з коробки:
- генерація секрету й QR-коду для прив'язки;
- перевірка 6-значного коду при вході;
- одноразові recovery codes на випадок втрати пристрою.
// Fortify вмикає features:
Features::twoFactorAuthentication(['confirm' => true]),
Під капотом - пакет pragmarx/google2fa. Важливо: зберігати секрет зашифрованим, давати recovery-коди, за бажанням «запам'ятати пристрій».
Докладніше в документації: Двофакторна автентифікація (Fortify)
Laravel Scout - драйверна абстракція повнотекстового пошуку. Додаєте трейт до моделі - і вона автоматично синхронізується з пошуковим індексом (Meilisearch, Algolia, Elasticsearch, навіть database).
class Post extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return ['title' => $this->title, 'body' => $this->body];
}
}
Post::search('laravel queues')->paginate(15);
- Індекс оновлюється на події моделі (краще - через чергу).
- Початкове наповнення:
php artisan scout:import "App\Models\Post". - Meilisearch дає швидкий typo-tolerant пошук «з коробки»; Elasticsearch - складніші аналітичні запити.
Value Object - невеликий незмінний (immutable) об'єкт, що представляє концепцію домену й порівнюється за значенням, а не за ідентичністю (на відміну від Entity з id).
final class Money
{
public function __construct(
public readonly int $cents,
public readonly string $currency,
) {}
public function add(Money $other): self
{
return new self($this->cents + $other->cents, $this->currency);
}
}
Переваги: інкапсуляція правил (валюта, валідація email), самодокументований код, безпека (незмінність). У Laravel VO зручно зберігати через Custom Casts, перетворюючи між колонкою БД та об'єктом.
CSP - HTTP-заголовок, що визначає, з яких джерел дозволено завантажувати ресурси (скрипти, стилі, зображення). Це потужний захист від XSS: навіть якщо зловмисник впровадить <script>, браузер не виконає його, якщо джерело не дозволене.
Content-Security-Policy: default-src 'self';
script-src 'self' https://cdn.example.com;
img-src 'self' data:;
У Laravel заголовок додають через middleware (вручну або пакетом на кшталт spatie/laravel-csp).
Практики:
- Уникати
'unsafe-inline'- використовувати nonce для інлайн-скриптів. - Спершу режим report-only (
Content-Security-Policy-Report-Only) зі збором звітів, щоб не зламати сайт.
BDD розширює TDD, зміщуючи фокус на поведінку системи з погляду бізнесу/користувача, а не на технічні деталі. Сценарії описують зрозумілою мовою (Gherkin: Given-When-Then).
Scenario: Успішний вхід
Given користувач зареєстрований
When він вводить правильні дані
Then він потрапляє на дашборд
У PHP-екосистемі - Behat. Pest також заохочує «describe behavior» стиль:
it('redirects to dashboard after login', function () {
// ...
});
Цінність BDD - спільна мова між розробниками, QA та бізнесом; тести стають живою документацією очікуваної поведінки.
Найчастіші причини:
- Великий серіалізований стан - Livewire серіалізує всі public-властивості в кожен запит. Тримайте в public лише необхідне; похідні дані вираховуйте у computed properties (
#[Computed]). - Надмірний
wire:model.live- кожне натискання шле запит. Використовуйте deferred або.debounce. - Відсутність
wire:keyу циклах - ламає узгодження DOM, спричиняє «стрибки» та зайвий ререндер. - «Божественні» компоненти - розбивайте на дрібніші, ізольовані.
- Завантаження всіх записів - використовуйте пагінацію та
#[Lazy]-компоненти.
@foreach ($posts as $post)
<livewire:post-row :post="$post" :key="$post->id" />
@endforeach
Також допомагають islands (частковий ререндер) та винесення важкої логіки в черги замість синхронних дій.
Для Livewire - хелпер livewire() (із pest-plugin-livewire):
livewire(SearchPosts::class)
->set('query', 'laravel')
->assertSee('Laravel Queues')
->call('clear')
->assertSet('query', '')
->assertDispatched('posts-updated');
Для Filament - спершу автентифікуйте користувача, потім тестуйте сторінки ресурсів:
livewire(CreatePost::class)
->fillForm(['title' => 'Hello'])
->call('create')
->assertHasNoFormErrors();
livewire(ListPosts::class)
->callAction(TestAction::make('publish')->table($post))
->assertNotified();
Ключові асерти: assertSet, assertSee, assertDispatched, assertHasFormErrors, assertCanSeeTableRecords.
Одна команда робить більшість:
php artisan optimize # config + route + view + event cache
Окремо:
php artisan config:cache # об'єднує конфіг у один файл
php artisan route:cache # компілює маршрути
php artisan view:cache # прекомпілює Blade
php artisan event:cache # кеш мапінгу подій/слухачів
composer install --no-dev --optimize-autoloader
Додатково: увімкнений OPcache (а краще з JIT), prebuilt ассети (npm run build).
Підводний камінь: після config:cache виклики env() поза config/ повертають null - усі env-значення мають читатися лише у конфіг-файлах. На деплої не забути php artisan optimize:clear перед повторним кешуванням.
Головний ризик - несумісність схеми зі старим кодом під час деплою та блокування таблиць.
Безпечні зміни (expand → migrate → contract):
- Додати нову колонку (nullable) - старий код працює.
- Задеплоїти код, що пише і в стару, і в нову.
- Перенести дані (фоновий job), перемкнути читання.
- Окремим релізом видалити стару колонку.
Практики:
- Не покладатися на
migrate:rollbackу проді -down()може втрачати дані. Краще forward-fix. - Великі
ALTERна величезних таблицях блокують → онлайн-міграції (pt-online-schema-change, gh-ost). php artisan migrate --forceу пайплайні;--isolated, щоб не виконати паралельно на кількох воркерах.- Бекап перед руйнівними операціями; прогін міграцій на staging.
Реплікація розвантажує основний сервер: запис іде на primary, читання - на replicas. Laravel маршрутизує запити автоматично, якщо в конфізі з'єднання задані секції read/write:
'mysql' => [
'read' => ['host' => ['10.0.0.2', '10.0.0.3']], // репліки
'write' => ['host' => ['10.0.0.1']], // primary
'sticky' => true,
// ...спільні параметри
],
SELECT→ репліка,INSERT/UPDATE/DELETE→ primary.sticky => trueкритично важливе: після запису в межах того ж запиту читання теж піде з primary, інакше через replication lag можна прочитати застарілі дані.- Реплікація асинхронна → завжди закладайте можливе відставання реплік у логіці.
Cache Stampede (dogpile) - коли популярний ключ кешу протермінувався, і сотні одночасних запитів кидаються перераховувати важке значення одночасно, перевантажуючи БД.
Рішення в Laravel:
// блокування: лише один процес перераховує, інші чекають результат
$value = Cache::lock('report:lock', 10)->block(5, function () {
return Cache::remember('report', 3600, fn () => $this->heavyReport());
});
Інші стратегії:
Cache::flexible()(stale-while-revalidate) - віддає «протухле» значення, поки одне фонове оновлення його перераховує.- Розмазування TTL (jitter), щоб ключі не протухали одночасно.
- Прогрів кешу (cache warming) за розкладом, а не за запитом користувача.
Завдання можуть падати через тимчасові збої (мережа, rate limit) - потрібна стратегія повторів і обробки остаточних провалів.
class CallApi implements ShouldQueue
{
public int $tries = 5; // спроб
public int $maxExceptions = 2;
public int $timeout = 30;
// прогресивна затримка між спробами
public function backoff(): array
{
return [10, 30, 60]; // 10с, 30с, 60с...
}
public function failed(Throwable $e): void
{
// викликається після вичерпання спроб
}
}
- Остаточно провалені завдання осідають у таблиці
failed_jobs. php artisan queue:retry all- повторити,queue:flush- очистити.releaseAfter,WithoutOverlapping,RateLimitedmiddleware керують поведінкою.- Ідемпотентність (idempotency) обов'язкова - бо завдання може виконатися повторно.
Contextual binding дозволяє віддавати різні реалізації одного інтерфейсу залежно від класу, що його запитує.
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('local'));
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('s3'));
Тобто PhotoController отримає локальний диск, VideoController - S3, хоча обидва просять Filesystem.
Споріднені можливості:
giveTagged()- впорснути всі сервіси з певним тегом.- Прив'язка примітивів:
->needs('$apiKey')->give(config('services.x.key')).
Корисно, коли одна абстракція має кілька конфігурацій у різних частинах застосунку.
PHP-FPM - «shared nothing»: кожен запит стартує з чистого стану, наприкінці все звільняється. Просто, безпечно, але є оверхед бутстрапу фреймворку на кожному запиті.
Octane тримає застосунок у пам'яті між запитами → кратно вищий throughput і нижча латентність.
| PHP-FPM | Octane | |
|---|---|---|
| Стан між запитами | чистий | зберігається |
| Throughput | нижчий | значно вищий |
| Ризик витоків стану | немає | є |
| Складність деплою | проста | вища (воркери, рестарти) |
Ціна Octane: треба остерігатися «протікання» стану (статика, синглтони, глобальні змінні), правильно скидати/перезапускати воркери, уважно з пам'яттю. FPM лишається розумним дефолтом, доки немає потреби в екстремальній продуктивності.
- Ресурсна модель URL: іменники в множині (
/posts,/posts/{id}/comments), дія - через HTTP-метод, а не в URL. - Коректні статус-коди: 200/201/204, 422 (валідація), 401/403, 404, 429.
- API Resources для відповіді - щоб відв'язати JSON від схеми БД і контролювати формат.
- Версіонування (
/v1) із самого старту. - Пагінація, фільтрація, сортування через query-параметри; не віддавати все одразу.
- Consistent error format - єдина структура помилок (Laravel дає
{ "message": ..., "errors": {...} }для 422). - Автентифікація через Sanctum/Passport, rate limiting на маршрутах.
- Idempotency для небезпечних повторюваних операцій (платежі).
- Документація (OpenAPI/Scribe) і контрактні тести.