Вопросы по PHP на собеседовании: Senior — часть 3/3 с разбором ответов
Это часть 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?
- 2. Как работает Garbage Collector в PHP?
- 3. Расскажите про асинхронный PHP: Fibers, ReactPHP, Swoole
- 4. Микросервисы vs монолит: когда что выбирать?
- 5. CQRS и Event Sourcing — зачем и когда?
- 6. Как тестировать PHP-код: unit, integration, mocks vs stubs
- 7. Как подходить к рефакторингу legacy-кода?
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 — ассистент, который помогает на реальных технических собеседованиях в реальном времени.
- Зайди на sobesai.app
- Скачай и установи приложение
- Запусти во время подготовки — проверь себя по вопросам из всех трёх частей
- На реальном собесе Sobes AI поможет структурировать ответ про CQRS или вспомнить разницу между Fibers и генераторами
- Работает незаметно — интервьюер не узнает
Это часть 3 из 3. ← Часть 1: Junior | ← Часть 2: Middle
Готовитесь к собеседованию?
Sobes AI слушает вопросы интервьюера и генерирует ответы в реальном времени.
Скачать Sobes AI