Массовое сохранение работает на staging, но в проде молча теряет строки. Определите причину.
Экран массового редактирования отправляет 900 строк одной HTML-формой. На staging сохраняются все 900. В проде — только первые 500: ничего не выбрасывается, лог приложения чист, а эндпоинт по-прежнему отвечает 204.
Ограничения: тот же тег релиза, та же база, тот же код на обеих машинах. Серверы готовили разные люди. display_errors в проде выключен.
// POST body: items[0][id]=..&items[0][qty]=..&items[1][id]=.. ... 900 строк
public function bulkUpdate(Request $request): Response
{
$items = $request->input('items', []);
foreach ($items as $item) {
$this->repo->setQty((int) $item['id'], (int) $item['qty']);
}
return response()->noContent();
}
Определите причину.
PHP перестаёт разбирать тело, как только построил max_input_vars переменных, — по умолчанию их 1000. Каждый items[i][id] и items[i][qty] — отдельная переменная, поэтому 900 строк это 1800, и всё после первых 500 строк отбрасывается ещё до контроллера; в error-лог FPM попадает лишь предупреждение. На staging лимит подняли руками. Шлите JSON, которого лимит не касается, и выкатывайте php.ini вместе с релизом.
- ✗Считать, что обрезанный
$_POSTобязан выбросить исключение, — PHP молча отбрасывает лишний ввод - ✗Считать строки формы, а не входные переменные, из-за чего 900 строк кажутся ниже лимита в 1000
- ✗Оставлять
php.iniрукотворным состоянием сервера, из-за чего staging и прод незаметно расходятся
- →Почему тело JSON, прочитанное из
php://input, вообще не попадает подmax_input_vars? - →Что происходит с
$_POST, когда тело большеpost_max_size, и чем это отличается от нашего случая?
Считайте не строки, а входные переменные.
Каждая строка формы шлёт items[i][id] и items[i][qty] — это две переменные. 900 строк = 1800 переменных. max_input_vars по умолчанию равен 1000: как только парсер построил тысячу, он перестаёт разбирать тело и молча выбрасывает остаток. 1000 переменных = ровно 500 полных строк — то самое, что вы видите.
Ни исключения, ни ошибки: PHP пишет только предупреждение, и только в error-лог движка.
grep 'Input variables exceeded' /var/log/php8.3-fpm.log
# PHP Warning: Unknown: Input variables exceeded 1000. To increase the limit
# change max_input_vars in php.ini.
php -i | grep max_input_vars # prod -> 1000 (значение по умолчанию)
php -i | grep max_input_vars # staging -> 5000 (кто-то поднял руками)
Поднять лимит в проде — это залатать симптом: следующая пачка на 3000 строк упадёт так же. Настоящее лечение двойное.
// 1. Слать тело как JSON: max_input_vars разбирает только urlencoded и multipart,
// а json_decode() читает php://input и под лимит не попадает вообще.
$items = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR)['items'];
// 2. Не доверять размеру пачки — валидировать и разбивать её на страницы явно.
И организационно: php.ini должен ехать вместе с релизом (образ, конфиг-менеджмент), а не набираться руками на каждой машине. Пока это состояние сервера, staging и прод будут расходиться, и расхождение будет вылезать вот такими молчаливыми потерями данных.