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

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

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

57 питань

Підключення оголошуються в 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) не вимагає переписування коду - лише конфіг.

Докладніше в документації: Зберігання файлів

Драйвер задається в config/session.php. На одному сервері різниці майже немає, на кількох - вона вирішальна.

file - файли на диску сервера. За балансувальником користувач після кожного запиту може потрапити на інший сервер, де його сесії немає, тож його «розлогінює» через раз.

cookie - усе зберігається в самій куці, зашифрованій. Спільного сховища не треба, але обсяг обмежений ~4 КБ, і дані їздять у кожному запиті.

database - таблиця sessions, спільна для всіх серверів. Працює надійно, ціна - запит на кожен запит.

redis - спільне сховище в памʼяті. Стандартний вибір для кількох серверів: швидко й без навантаження на базу.

Що ще ламається на кількох серверах, крім драйвера:

  • APP_KEY має бути однаковий на всіх серверах, інакше куки, зашифровані одним, не розшифрує інший.
  • Той самий Redis мають бачити всі вузли - окремі інстанси не допоможуть.

Дві деталі, що псують життя окремо від масштабування. Сесія блокується на час запиту, тож кілька паралельних AJAX-запитів шикуються в чергу - для читання це знімається ->block() або сесією без запису. І в API сесій зазвичай узагалі не має бути: токен не потребує стану на сервері, а SESSION_DRIVER для stateless-запитів - зайва робота.

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

Файли в lang/ перекладають інтерфейс - кнопки, підписи, помилки. Для контенту з бази - назв категорій, описів товарів - потрібен інший підхід, і вибір між трьома.

1. Колонки на кожну мову - найпростіше:

Schema::table('categories', function (Blueprint $table) {
    $table->string('name_uk');
    $table->string('name_en');
});

Працює, поки мов дві. Кожна нова - міграція й правка всіх запитів.

2. JSON-колонка:

$table->json('name');   // {"uk": "Вакансії", "en": "Jobs"}

protected function casts(): array
{
    return ['name' => 'array'];
}

Нова мова не потребує міграції. Мінус - пошук і сортування за перекладом стають незручними, хоча PostgreSQL і MySQL уміють індексувати JSON-шляхи.

3. Окрема таблиця перекладів - рядок на пару «запис + мова». Найгнучкіше: пошук і сортування звичайні, мов скільки завгодно. Ціна - join у кожному запиті.

Що врахувати незалежно від вибору:

  • Запасний варіант. Якщо перекладу немає, показувати мову за замовчуванням, а не порожнє місце.
  • URL. Мову зазвичай виносять у шлях (/en/jobs), бо це дає окремі адреси для індексації - на відміну від зберігання вибору лише в сесії.
  • hreflang у розмітці, щоб пошук розумів звʼязок між версіями.
  • Не лише текст. Формати дат, чисел і валют теж локальні; Carbon::setLocale() і Number::format() це враховують.

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

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).

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+.

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

Порядок кроків важливіший за їхній перелік: та сама команда, виконана не там, ламає деплой.

Типова послідовність:

composer install --no-dev --optimize-autoloader
npm ci && npm run build

php artisan migrate --force

php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

php artisan queue:restart

Чому саме так:

  • Кешування - після викладення коду. Інакше в кеш потрапить попередня версія конфігурації й маршрутів.
  • --force для міграцій потрібен, бо в проді Artisan питає підтвердження, а деплой неінтерактивний. Це стосується лише migrate; migrate:fresh у проді не запускають ніколи.
  • queue:restart обовʼязковий. Воркери - довгоживучі процеси: вони тримають у памʼяті стару версію коду й працюватимуть з нею, поки їх не перезапустити. Це найчастіша причина «код виклали, а черга робить по-старому».

Про що забувають:

  • Порядок міграції та коду. Видалення колонки у тому ж випуску, що й код, який її ще читає, ламає застосунок у проміжку між кроками. Такі зміни розводять на два випуски.
  • storage:link після першого розгортання, інакше публічні файли не віддаються.
  • Права на storage і bootstrap/cache - веб-сервер має писати в обидві.
  • php artisan down дає режим обслуговування, але з ним застосунок недоступний; безпростійний деплой роблять перемиканням симлінка на новий реліз.

Готові рішення - Envoyer, Deployer, Laravel Cloud - роблять саме це: викладають реліз поруч і перемикають симлінк, коли він готовий.

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

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

Вакансії Laravel рівня Middle

Усі вакансії Laravel Middle

Full Stack Developer (PHP, React, Middle, Middle+)

Full Stack розробник для підтримки та розвитку аналітичного продукту перевірки контрагентів. Робота зі складною бізнес-логікою, базами даних та інтеграцією AI-рішень. Стек: PHP 8.x (Laravel/Symfony), React, MySQL, REST API. Вимоги: 3+ років комерційного досвіду, глибоке розуміння SQL, Git, Docker, CI/CD, Linux, OWASP.

Full Stack Developer (PHP / React) Middle / Middle+

Full Stack розробник для аналітичного продукту компанії з перевірки контрагентів та оцінки ризиків. Основна робота: підтримка та розвиток існуючої системи, рефакторинг legacy-коду, розробка бекенду на PHP/Laravel/Symfony і фронтенду на React, оптимізація баз даних MySQL та SQL-запитів, інтеграція AI-сервісів. Вимоги: 3+ років комерційної розробки, PHP 8.x, Laravel або Symfony, React, REST API, Docker, Git, Linux. Англійська B1+.

Програміст PHP (інтерн)

Вакансія на посаду інтерна PHP розробника для початківців з теоретичною базою ООП та базовими знаннями PHP. Потрібні навички Git/GitHub, власні проєкти. Стажування в офісі під керівництвом менторів з перспективою переходу на посаду Junior разробника.

Питання рівня Middle з реальних співбесід Laravel і PHP - 57 питань у 39 темах, розібраних із відповідями. Нижче - теми цього рівня та сусідні рівні, якщо хочете звузити або розширити підготовку.

Інші рівні
Junior 52 Senior 62