Вопросы по PHP: Senior — 7 вопросов с ответами 2026
S.
Sobes AI

Вопросы по PHP на собеседовании: Senior — часть 3/3 с разбором ответов

05.03.2026 | 5 мин чтения | 2 просмотров

Это часть 3 из 3. ← Часть 1: Junior | ← Часть 2: Middle

TL;DR: 7 вопросов для Senior PHP-разработчиков — PHP 8.x фичи, Garbage Collector, Fibers, микросервисы, CQRS, тестирование и рефакторинг legacy. Не пересказ документации, а то, что реально проверяют на собесах. По данным собеседований 2024–2026.

Как пользоваться: Открой перед собеседованием → пройдись по вопросам → если follow-up не вызывает затруднений — ты готов. Если где-то буксуешь — знаешь, куда копать.

Это финальная часть серии. Первая — Junior, вторая — Middle. Senior-вопросы — это не столько конкретные ответы, сколько умение рассуждать о trade-offs, аргументировать решения и видеть систему целиком.

Содержание


1. Какие ключевые фичи появились в PHP 8.x?

Проверяет, следит ли кандидат за развитием языка. По отзывам на Glassdoor и Хабре за 2024–2026, этот вопрос задают на 60%+ Senior-собеседований.

PHP 8.0 — переломный релиз:

// Union types
function processInput(string|int $input): string|false { /* ... */ }

// Named arguments
htmlspecialchars(string: $text, double_encode: false);

// Match expression (строгое сравнение, возвращает значение)
$status = match($code) {
    200 => 'OK',
    404 => 'Not Found',
    500 => 'Server Error',
    default => 'Unknown',
};

// Nullsafe operator
$city = $user?->getAddress()?->getCity();

// Constructor promotion
class User {
    public function __construct(
        private string $name,
        private string $email,
    ) {}
}

PHP 8.1:

// Enums — полноценные перечисления
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
    case Banned = 'banned';

    public function label(): string {
        return match($this) {
            self::Active => 'Активен',
            self::Inactive => 'Неактивен',
            self::Banned => 'Заблокирован',
        };
    }
}

// Readonly properties
class Product {
    public function __construct(
        public readonly string $name,
        public readonly float $price,
    ) {}
}

// Fibers — кооперативная многозадачность
$fiber = new Fiber(function (): void {
    $value = Fiber::suspend('hello');
    echo "Получено: $value";
});

$result = $fiber->start();  // 'hello'
$fiber->resume('world');    // Получено: world

// Intersection types
function process(Countable&Iterator $collection): void { /* ... */ }

PHP 8.2: readonly классы, true/false/null как standalone типы, DNF types.

PHP 8.3: typed class constants, json_validate(), #[\Override] attribute.

PHP 8.4: property hooks, new без скобок в цепочке вызовов, array_find().

Типичная ошибка: Кандидат знает union types, но не может объяснить, когда использовать Enums вместо констант, или не слышал про Fibers.

Follow-up: В чём разница между Enum и набором констант в классе? (Enum — полноценный тип, можно использовать в type hints. Enum может иметь методы и реализовывать интерфейсы. Константы — просто значения.)


2. Как работает Garbage Collector в PHP?

Показывает глубокое понимание управления памятью. Один из вопросов, отличающих Senior от Middle, по разборам PHP-собеседований на Хабре.

PHP использует два механизма очистки памяти:

1. Reference counting — основной механизм. Каждый zval имеет refcount. Когда refcount падает до 0 — память освобождается немедленно.

$a = "hello"; // refcount = 1
$b = $a;      // refcount = 2 (COW)
unset($a);    // refcount = 1
unset($b);    // refcount = 0 → память свободна

2. Cycle collector — решает проблему циклических ссылок, где refcount никогда не достигнет 0:

// Циклическая ссылка
$a = new stdClass();
$b = new stdClass();
$a->ref = $b;
$b->ref = $a;
unset($a, $b);
// refcount обоих объектов = 1, но они недоступны
// Без GC — утечка памяти

GC запускается, когда буфер потенциальных «мусорных» корней (roots) заполняется (по умолчанию 10 000 элементов). Использует алгоритм на основе обхода графа.

// Управление GC
gc_enable();            // включить (по умолчанию включён)
gc_disable();           // отключить
gc_collect_cycles();    // принудительный запуск
gc_status();            // статистика: runs, collected, roots

Когда GC создаёт проблемы:

  • В long-running скриптах (workers, daemons) — объекты копятся
  • При обработке больших графов объектов — GC может занять значительное время
  • В PHP-FPM это менее критично — память очищается после каждого запроса (shared-nothing)

Типичная ошибка: Кандидат думает, что PHP «сам разберётся с памятью» и не понимает, когда и почему возникают утечки.

Follow-up: Как найти утечку памяти в PHP long-running процессе? (memory_get_usage(true) в цикле, Xdebug profiler, Blackfire. Искать объекты с циклическими ссылками и незакрытые ресурсы.)


3. Расскажите про асинхронный PHP: Fibers, ReactPHP, Swoole

Проверяет знание современных подходов к concurrency в PHP. Тема активно растёт — по данным обсуждений на Reddit и Хабре, всё больше компаний используют async PHP.

PHP традиционно синхронный — один запрос, один процесс, блокирующий I/O. Три подхода к асинхронности:

Fibers (PHP 8.1+) — низкоуровневый примитив кооперативной многозадачности:

$fiber = new Fiber(function (): string {
    // Fiber может приостановиться
    $data = Fiber::suspend();
    return "Обработано: $data";
});

$fiber->start();           // запуск, выполняется до suspend()
$fiber->resume('input');   // возобновление с данными
echo $fiber->getReturn();  // "Обработано: input"

Fibers сами по себе не делают код асинхронным — это строительный блок для библиотек. Amp v3 и ReactPHP используют Fibers под капотом.

ReactPHP — event loop для PHP. Неблокирующий I/O:

// HTTP-сервер на ReactPHP
$http = new React\Http\HttpServer(function (
    Psr\Http\Message\ServerRequestInterface $request
) {
    return React\Http\Message\Response::plaintext("Hello\n");
});

$socket = new React\Socket\SocketServer('127.0.0.1:8080');
$http->listen($socket);

Swoole — расширение на C, предоставляет корутины, TCP/UDP сервер, WebSocket. Самый производительный вариант, но требует отдельного расширения.

Когда что выбирать:

  • Fibers + Amp/ReactPHP — если нужен async в существующем проекте без смены инфраструктуры
  • ReactPHP — WebSocket-серверы, real-time приложения
  • Swoole — максимальная производительность, готовы к нестандартной инфраструктуре

Типичная ошибка: Кандидат путает Fibers с корутинами или думает, что Fibers сами по себе делают PHP асинхронным.

Follow-up: Чем Fibers отличаются от генераторов? (Генераторы приостанавливаются только на yield в самой функции-генераторе. Fibers могут приостанавливаться в любом месте стека вызовов — из вложенной функции.)


Три вопроса про PHP под капотом позади. Дальше — архитектурные вопросы, где нет единственного правильного ответа, но есть правильный подход к рассуждению.


4. Микросервисы vs монолит: когда что выбирать?

Архитектурный вопрос, который показывает зрелость кандидата. Senior должен не продвигать «модную» архитектуру, а обосновывать выбор trade-offs. Часто задают на собеседованиях в крупных компаниях по данным Glassdoor.

Монолит подходит, когда:

  • Команда маленькая (до 5–7 разработчиков)
  • Продукт на ранней стадии, требования нестабильны
  • Нет опыта эксплуатации распределённых систем
  • Laravel/Symfony монолит с модулями — отличный выбор для большинства проектов

Микросервисы оправданы, когда:

  • Несколько команд работают над разными частями продукта
  • Разные части системы масштабируются независимо
  • Нужны разные технологии для разных задач
  • Есть DevOps-инфраструктура (CI/CD, мониторинг, трейсинг)

Промежуточный вариант — модульный монолит:

app/
├── Modules/
│   ├── User/
│   │   ├── Controllers/
│   │   ├── Services/
│   │   ├── Models/
│   │   └── Routes/
│   ├── Order/
│   │   ├── Controllers/
│   │   ├── Services/
│   │   └── Events/
│   └── Payment/
│       └── ...

Модули общаются через события и интерфейсы, но деплоятся как один процесс. Если позже нужно выделить сервис — модуль уже готов к отделению.

Коммуникация между микросервисами:

  • Синхронная: REST, gRPC — для запрос-ответ сценариев
  • Асинхронная: RabbitMQ, Kafka — для событий и очередей
  • В PHP-экосистеме: Laravel Octane + gRPC или REST + RabbitMQ

Типичная ошибка: Кандидат безусловно продвигает микросервисы, не упоминая стоимость: distributed tracing, eventual consistency, сетевые сбои, сложность деплоя.

Follow-up: Как реализовать транзакцию, затрагивающую два микросервиса? (Saga pattern — цепочка локальных транзакций с компенсирующими действиями при ошибке.)


5. CQRS и Event Sourcing — зачем и когда?

Проверяет знание архитектурных паттернов для сложных доменов. По отзывам на Хабре и Reddit, вопросы по CQRS всё чаще задают на Senior PHP-позициях в финтехе и e-commerce.

CQRS (Command Query Responsibility Segregation) — разделение модели на две:

  • Command model — для записи (create, update, delete)
  • Query model — для чтения (оптимизированные проекции)
// Command — изменяет состояние
class PlaceOrderCommand {
    public function __construct(
        public readonly int $userId,
        public readonly array $items,
    ) {}
}

class PlaceOrderHandler {
    public function handle(PlaceOrderCommand $command): void {
        $order = Order::create($command->userId, $command->items);
        $this->orderRepository->save($order);
        $this->eventBus->dispatch(new OrderPlaced($order));
    }
}

// Query — читает без побочных эффектов
class GetOrdersQuery {
    public function __construct(
        public readonly int $userId,
    ) {}
}

class GetOrdersHandler {
    public function handle(GetOrdersQuery $query): array {
        // Может читать из другой БД, кэша, ElasticSearch
        return $this->readDb->fetchOrdersForUser($query->userId);
    }
}

Event Sourcing — вместо хранения текущего состояния, сохраняем все события:

OrderPlaced → ItemAdded → ItemRemoved → OrderPaid → OrderShipped

Текущее состояние = replay всех событий. Даёт полный audit trail и возможность «перемотать» состояние.

Когда CQRS:

  • Читающая и пишущая нагрузки сильно различаются
  • Нужны разные модели для чтения (отчёты, поиск, API)

Когда Event Sourcing:

  • Нужна полная история изменений (финансы, аудит)
  • Сложная доменная логика с отменой/возвратом

Когда НЕ нужно:

  • CRUD-приложения без сложной бизнес-логики
  • Маленькие проекты — оверхед не окупится

Типичная ошибка: Кандидат описывает CQRS и Event Sourcing как неразделимую пару. На практике CQRS часто используют без Event Sourcing.

Follow-up: Какие проблемы создаёт Event Sourcing? (Eventual consistency, сложность запросов к текущему состоянию, рост хранилища событий, версионирование событий.)


6. Как тестировать PHP-код: unit, integration, mocks vs stubs

Тестирование — один из ключевых навыков, который ожидают от Senior на большинстве собеседований. По данным обсуждений на Reddit — отсутствие опыта тестирования на Senior-уровне воспринимается как серьёзный пробел.

Unit-тесты — тестируют один класс/метод в изоляции. Быстрые, без внешних зависимостей.

class PriceCalculatorTest extends TestCase {
    public function test_applies_discount(): void {
        $calculator = new PriceCalculator();
        $result = $calculator->calculate(100.0, discount: 15);
        $this->assertEquals(85.0, $result);
    }

    public function test_rejects_negative_price(): void {
        $calculator = new PriceCalculator();
        $this->expectException(InvalidArgumentException::class);
        $calculator->calculate(-10.0, discount: 0);
    }
}

Integration-тесты — тестируют взаимодействие компонентов (с БД, API, файлами).

class OrderServiceTest extends TestCase {
    use RefreshDatabase;

    public function test_creates_order_with_items(): void {
        $user = User::factory()->create();
        $product = Product::factory()->create(['price' => 50.0]);

        $service = app(OrderService::class);
        $order = $service->createOrder($user, [
            ['product_id' => $product->id, 'quantity' => 2],
        ]);

        $this->assertDatabaseHas('orders', ['user_id' => $user->id]);
        $this->assertEquals(100.0, $order->total);
    }
}

Mock vs Stub:

  • Stub — возвращает заранее заданные данные. Заменяет зависимость для изоляции.
  • Mock — stub + проверка, что метод был вызван с определёнными аргументами.
// Stub — просто подменяем поведение
$gateway = $this->createStub(PaymentGateway::class);
$gateway->method('charge')->willReturn(true);

// Mock — проверяем, что charge был вызван
$gateway = $this->createMock(PaymentGateway::class);
$gateway->expects($this->once())
    ->method('charge')
    ->with(100.0, 'USD')
    ->willReturn(true);

Пирамида тестирования:

  • Много unit-тестов (быстрые, дешёвые)
  • Меньше integration-тестов (медленнее, но проверяют реальное взаимодействие)
  • Мало E2E-тестов (самые медленные, но проверяют весь flow)

Типичная ошибка: Кандидат путает mock и stub, или считает, что «100% покрытие = качественные тесты». Покрытие не гарантирует качества — важно тестировать поведение, а не реализацию.

Follow-up: Когда mock-тесты приносят больше вреда, чем пользы? (Когда тесты привязаны к реализации, а не к поведению. Каждый рефакторинг ломает тесты, хотя поведение не изменилось.)


7. Как подходить к рефакторингу legacy-кода?

Финальный вопрос серии. Показывает зрелость и опыт — Senior должен уметь не только писать новый код, но и улучшать существующий. По разборам PHP-собеседований за 2024–2026, этот вопрос задают в компаниях с большой кодовой базой.

Рефакторинг legacy — это не «переписать всё с нуля». Пошаговый подход:

1. Покрыть тестами — прежде чем менять код, убедись, что текущее поведение зафиксировано. Характеризационные тесты (characterization tests) — тесты, которые описывают текущее поведение, даже если оно «неправильное».

// Характеризационный тест — фиксируем текущее поведение
public function test_legacy_discount_calculation(): void {
    $service = new LegacyDiscountService();
    // Не знаем, правильно ли 42.5, но так работает сейчас
    $this->assertEquals(42.5, $service->calculate(50, 'VIP'));
}

2. Выделить seams — точки в коде, где можно подменить поведение без изменения логики. Обернуть внешние зависимости в интерфейсы.

3. Strangler Fig Pattern — постепенная замена legacy-кода новым:

[Legacy Monolith]
       ↓
[Facade/Router] → [New Module] (новая логика)
       ↓
[Legacy Module] (старая логика — пока живёт)

Новый функционал пишется в новых модулях. Старый код постепенно заменяется. Обе системы работают параллельно.

4. Маленькие шаги — каждый рефакторинг должен быть отдельным коммитом/PR. Не мешать рефакторинг с добавлением фич.

Аргументация перед бизнесом:

  • «Если не рефакторить, добавление фичи X займёт 3 недели вместо 3 дней»
  • Метрики: cyclomatic complexity, количество багов в модуле, время на добавление фичи
  • Не «надо переписать всё», а «давайте улучшим этот модуль перед добавлением фичи Y»

Типичная ошибка: Кандидат предлагает «переписать всё с нуля» — это почти всегда ошибка. Или начинает рефакторить без тестов.

Follow-up: Что такое Strangler Fig Pattern и когда он не подходит? (Не подходит, когда legacy-код тесно связан и невозможно выделить модули для постепенной замены.)


Итого Senior: 7 вопросов. Фокус — PHP 8.x, управление памятью, async, архитектурные решения, тестирование, legacy. На Senior-уровне важен не столько правильный ответ, сколько способность аргументировать trade-offs.

Как готовиться

  • PHP 8.x — напиши мини-проект с Enums, readonly классами, Fibers. Прочитай RFC каждой фичи — там объяснено, зачем она нужна.
  • Архитектура — прочитай «Implementing Domain-Driven Design» (Vaughn Vernon) или «Clean Architecture» (Robert C. Martin). Примеры адаптируй на PHP.
  • Тестирование — если не пишешь тесты на работе, начни с open-source проекта. Попробуй покрыть тестами чужой код.
  • GC и память — Xdebug + Blackfire для профилирования. Запусти long-running скрипт и отследи потребление памяти.

Чего НЕ спрашивают

Несколько тем, которые часто встречаются в учебниках, но редко — на реальных собеседованиях:

  • mysql_connect / mysql_query — устаревшие функции, удалены в PHP 7. Никто не спрашивает.
  • Конкретный синтаксис регулярных выражений — могут спросить «что такое regex и зачем», но не попросят написать сложное выражение на доске.
  • Точные цифры лимитов (максимальный размер int, точность float) — спрашивают концепцию, не числа.
  • XML-парсинг в PHP — почти не спрашивают, JSON доминирует.
  • Встроенные функции для работы со строками/массивами по памяти — знать основные полезно, но заучивать 100 функций не нужно.

Как попробовать Sobes AI

Sobes AI — ассистент, который помогает на реальных технических собеседованиях в реальном времени.

  1. Зайди на sobesai.app
  2. Скачай и установи приложение
  3. Запусти во время подготовки — проверь себя по вопросам из всех трёх частей
  4. На реальном собесе Sobes AI поможет структурировать ответ про CQRS или вспомнить разницу между Fibers и генераторами
  5. Работает незаметно — интервьюер не узнает

Это часть 3 из 3. ← Часть 1: Junior | ← Часть 2: Middle

Готовитесь к собеседованию?

Sobes AI слушает вопросы интервьюера и генерирует ответы в реальном времени.

Скачать Sobes AI