PHP-разработчик
80 задач. Условие и варианты ответа открыты всем; проверка, подсказка и разбор — в клубе.
PHP 8: типы и поведение языка
- Значение null в массиве: что вернут isset() и array_key_exists()
- Массив и объект в аргументах функции: что изменится снаружи
- strict_types и строка в аргументе с типом int
- Два foreach подряд, второй без ссылки: что окажется в массиве
- Деление на ноль внутри try/catch (Exception)
- ?? и ?: на значении 0: что попадёт в переменные
- self и static в фабричном методе базового класса
- DateTime::modify() и границы отчётного периода
SQL и оптимизация запросов
- Список из 100 заказов дал 101 запрос в БД
- EXPLAIN показал type: ALL и key: NULL
- Индекс по created_at есть, но фильтр по дате его не использует
- Составной индекс (client_id, created_at) и фильтр только по дате
- LEFT JOIN, после которого пропали клиенты без заказов
- Пагинация с OFFSET 500000 отвечает секундами
- Индекс по status добавили — быстрее не стало, ночная загрузка замедлилась
- Deadlock при параллельном переводе бонусов между счетами
REST API и интеграции
- API отвечает 200 OK с телом {"success": false}
- GET /api/orders отдаёт все 400 000 заказов
- Повторный POST /orders создал второй заказ
- Новое обязательное поле в запросе сломало партнёров
- PUT с частичным телом обнулил поля профиля
- Запрос к партнёрскому CRM без таймаутов повесил воркеров
- Заказ помечается оплаченным по содержимому колбэка
- Заказ и сделка в CRM расходятся: HTTP-вызов внутри транзакции
Кэш, очереди и фоновые задачи
- Цену поменяли, а витрина час показывает старую
- Ключи в Redis без TTL: память кончилась, ключи начали пропадать
- Четырёхминутная выгрузка отчёта отдаёт 504
- Задача в очереди получила снимок данных вместо идентификатора
- Кэш списков каталога: какие ключи сбрасывать при изменении товара
- Воркер упал после записи в БД, но до подтверждения сообщения
- Всплеск latency в момент истечения TTL популярного ключа
- Списания в Redis теряются при восьми воркерах
Laravel: Eloquent, очереди и события
- Пользователь отправил is_paid=true в форме и получил оплаченный заказ
- Artisan-команда пересчёта бонусов падает с Allowed memory size exhausted
- После деплоя ключ платёжного API в сервисе стал null
- Подставив чужой номер в адрес /orders/{order}, пользователь видит чужой заказ
- Задача отправки письма падает с ModelNotFoundException для удалённых заказов
- Слушатель события в очереди не находит в базе только что созданный заказ
- Массовая отмена заказов не записала историю статусов и не отправила уведомления
- Сумма заказа в отчёте получается 10.199999999999999 вместо 10.20
- Удалившийся пользователь не может зарегистрироваться с тем же email
- Задача списания с $tries = 3 списала деньги трижды при таймауте банка
- Воркер очереди за сутки разрастается до 2 ГБ и начинает работать со старыми данными
- При двойном клике firstOrCreate создал две корзины одному пользователю
Legacy-код, SOLID и статический анализ
- С чего начать рефакторинг расчёта доставки без единого теста
- Функции legacy-модуля получают базу через global $db
- PHPStan сообщает Cannot call method on App\Models\User|null
- Половина комментариев на ревью — про пробелы, скобки и порядок use
- Проект на 3 000 файлов нужно перевести с синтаксиса PHP 7.4 на возможности PHP 8.2
- PHPStan на старом проекте выдал 4 000 ошибок, и его отключили через неделю
- Класс OrderManager на 5 000 строк знает про оплату, склад, письма и отчёты
- Опечатка в ключе массива $order['delivry'] неделю молча ломала расчёт доставки
- Включили declare(strict_types=1) во всех файлах, и в проде посыпались TypeError
- Модуль заказов решили переписать с нуля «за квартал», прошло три квартала
- Наследник класса доставки бросает исключение там, где базовый класс работал
- Замена CRM требует правок в 40 местах, где напрямую вызывается её HTTP-API
Nginx, PHP-FPM, Docker и эксплуатация
- После переезда на новый сервер PHP-процессы едят в разы больше CPU
- После деплоя сайт отвечает 502 Bad Gateway на все запросы
- Загрузка прайс-листа на 20 МБ падает с 413 Request Entity Too Large
- В продовом образе Laravel-приложения оказались PHPUnit, Faker и Xdebug
- В часы пик сайт отвечает 502 и 504, а в логе FPM: server reached pm.max_children
- max_execution_time подняли до 300 секунд, а отчёт всё равно обрывается на 60-й
- После деплоя сайт продолжает работать на старом коде
- После добавления второго сервера пользователей случайно разлогинивает
- Логи Laravel в контейнерах не попадают в систему сбора логов
- Память воркеров PHP-FPM медленно растёт, пока сервер не уходит в своп
- Ежедневный отчёт финансистам приходит три раза, по числу серверов
- Во время деплоя пользователи получают ошибки «Class not found»
Тестирование: PHPUnit и feature-тесты
- PHPUnit сообщает «No tests executed», хотя метод теста написан
- Тест прошёл, хотя метод возвращает строку «0» вместо числа 0
- Шесть копий теста валидации ИНН отличаются только входной строкой
- Feature-тест регистрации проходит один раз и падает при повторном запуске
- createMock падает: класс платёжного клиента объявлен final
- Как проверить обработку ответа 503 от службы доставки в Laravel
- Тест истечения подписки проходит в полдень и падает около полуночи
- Тест с Queue::fake() зелёный, а задача отправки чека в проде падает
- Тест исключения при оплате отменённого заказа никогда не проверяет исключение
- Тесты на SQLite в памяти зелёные, а запрос по JSON-колонке падает на MySQL в проде
- Покрытие модуля тарифов 95%, а Infection сообщает, что 60% мутантов выжили
- После включения php artisan test --parallel тесты загрузки файлов мешают друг другу
