Веб

Ошибка 500 PDO LIKE bindParam в PHP 8.4: как исправить

Решение проблемы с PDO LIKE и bindParam после обновления до PHP 8.4: 500 ошибка из-за передачи выражения в bindParam. Используйте bindValue, переменную $like или execute с массивом. Примеры кода и отладка.

Проблема с PDO LIKE и bindParam после обновления до PHP 8.4: запрос возвращает 500 ошибку

Есть сортировка по буквам брендов часов. При клике на букву (например, ‘H’) через jQuery отправляется AJAX POST-запрос на /admin/sortbya с параметром name равным этой букве.

Код jQuery:

javascript
$(document).on('click', ".jq-char", function() {
 bk = $(this).html();
 $.ajax({
 url: '/admin/sortbya',
 type: 'POST',
 data: { name: bk },
 cache: false,
 success: function (data) {
 console.log(data);
 $('.table-news').html(data);
 }
 }); 
});

В PHP обработчик вызывает метод:

php
public static function allA($name)
{
 $db = Db::getConnection();
 $sql = 'SELECT * FROM watch WHERE watch_brand like :name and sold !="yes"';
 $result = $db->prepare($sql);
 $result->bindParam(':name', "$name%", PDO::PARAM_STR);
 $result->setFetchMode(PDO::FETCH_ASSOC);
 $result->execute();
 return $result->fetchAll();
}

До обновления с PHP 7 всё работало корректно: запрос возвращал бренды, начинающиеся на указанную букву. Прямой SQL-запрос в БД работает. Если в функции просто вывести данные без PDO-запроса, ошибок нет. В чём причина проблемы с LIKE :name после перехода на PHP 8.4? Как исправить bindParam для LIKE с суффиксом ‘%’?

Проблема в том, что PDOStatement::bindParam привязывает переменную по ссылке, а в PHP 8.1+ передача выражения/литерала (например "namename%") вместо переменной даёт фатальную ошибку и 500. Исправления: заранее сформировать переменную с '%' (like = $name . ‘%’) и привязать её, либо использовать bindValue(), либо передать значение через execute([‘:name’ => $name . ‘%’]). Для отладки включите режим ошибок PDO (ERRMODE_EXCEPTION), чтобы увидеть реальное исключение.


Содержание


Краткое объяснение проблемы

Ваш код вызывает

php
$result->bindParam(':name', "$name%", PDO::PARAM_STR);

и это выглядело нормально на PHP 7, но после обновления до PHP 8.4 приводит к 500. Дело в том, что bindParam ожидает переменную, потому что привязка выполняется по ссылке и значение читается в момент execute(). Передача выражения/литерала (например “$name%”) — не переменная — приводит к ошибке типа “Cannot pass parameter 2 by reference” / “Only variables can be passed by reference”. См. документацию по bindParam для деталей: https://www.php.net/manual/en/pdostatement.bindparam.php.


Почему bindParam вызывает 500 в PHP 8.4

  • bindParam привязывает именно переменную (ссылка), а не копию значения. Значение будет взято при вызове execute(). Это ключевой момент: bindParam требует реальную переменную, а не выражение или строковый литерал. Подробно — в мануале PHP: https://www.php.net/manual/en/pdostatement.bindparam.php.
  • Передача выражения вроде “$name%” даёт ошибку передачи по ссылке; это часто документировано и обсуждалось в сообществах: пример обсуждения ошибки — https://stackoverflow.com/questions/31179556/bindparam-causes-a-fatal-error.
  • Для LIKE‑шаблонов нельзя писать ‘%:keyword%’ в SQL и надеяться, что плейсхолдер развернётся внутри строки; правильный подход — оставить плейсхолдер чистым и добавить ‘%’ в значение, либо использовать bindValue/execute. См. практический разбор: https://stackoverflow.com/questions/11068230/using-like-in-bindparam-for-a-mysql-pdo-query и сравнение bindParam vs bindValue: https://sqlpey.com/php/pdo-bindparam-vs-bindvalue-behavior/.

Коротко: PHP 8 строже относился к передачим по ссылке/временным значениям, поэтому выражение “$name%” при bindParam теперь даёт фатал.


Три рабочих решения для PDO LIKE и bindParam

Ниже — три корректных способа, отсортированы по простоте и распространённости.

  1. bindValue() — копирует значение сразу (рекомендуется для одиночного запроса)
php
$sql = 'SELECT * FROM watch WHERE watch_brand LIKE :name AND sold != "yes"';
$stmt = $db->prepare($sql);
$stmt->bindValue(':name', $name . '%', PDO::PARAM_STR);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Плюс: можно передавать выражение ($name . ‘%’) напрямую, так как bindValue принимает значение, а не ссылку.

  1. bindParam() с заранее сформированной переменной
php
$sql = 'SELECT * FROM watch WHERE watch_brand LIKE :name AND sold != "yes"';
$like = $name . '%';
$stmt = $db->prepare($sql);
$stmt->bindParam(':name', $like, PDO::PARAM_STR);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Плюс: bindParam удобен при повторных execute() в цикле (меняете $like и вызываете execute() снова). Главное — передавать именно переменную.

  1. Передача значения в execute() — самый короткий и часто используемый вариант
php
$sql = 'SELECT * FROM watch WHERE watch_brand LIKE :name AND sold != "yes"';
$stmt = $db->prepare($sql);
$stmt->execute([':name' => $name . '%']);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Плюс: компактно, читабельно, безопасно.

Важно: не писать в SQL ‘%:name%’ — плейсхолдеры не разворачиваются внутри строковых литералов, поэтому wildcard должен быть в значении, а не в SQL-литерале. Пример обсуждения: https://stackoverflow.com/questions/11068230/using-like-in-bindparam-for-a-mysql-pdo-query.


Рекомендации: ERRMODE, EMULATE_PREPARES и безопасность

  • Включите исключения для PDO, чтобы 500 превращался в понятное исключение во время разработки:
php
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  • Эмуляция подготовленных выражений:
php
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // предпочтительно

Иногда включение эмуляции (true) меняет поведение драйвера и может скрыть проблему, но это влияет на типизацию и безопасность — использовать как временный обходной путь, а не как постоянное решение.

  • Экранирование символов для LIKE: если пользователь может вводить %, _, или , и вы хотите искать буквальные символы, экранируйте их:
php
$raw = $name;
$raw = str_replace(['\\','%','_'], ['\\\\','\\%','\_'], $raw);
$like = $raw . '%';
$stmt->bindValue(':name', $like, PDO::PARAM_STR);

Или используйте SQL ESCAPE, если нужно.


Как тестировать и отлаживать (видимость 500)

  • На dev‑сервере включите отображение ошибок (display_errors=On) и режим исключений PDO. Либо поймайте исключение:
php
try {
 $stmt = $db->prepare($sql);
 $stmt->execute([':name' => $name . '%']);
 $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
 error_log($e->getMessage());
 http_response_code(500);
 echo 'DB error: ' . $e->getMessage(); // в dev; в prod логируйте и не выводите детали
}
  • Проверьте логи веб‑сервера (nginx/apache) и PHP‑error log: часто там будет точный текст ошибки — “Cannot pass parameter 2 by reference” и трейс.
  • Если AJAX получает пустой ответ 500, откройте Network → Response в DevTools, чтобы увидеть сообщение сервера (если вы выводите его), или смотрите server logs.

FAQ

В: Почему в PHP 7 этот трюк работал?
О: Возможные причины — разная конфигурация PDO (эмуляция подготовленных выражений), отличия драйвера базы или настройки отображения/логирования ошибок; иногда в старой среде ошибка просто не проявлялась из‑за других настроек. Тем не менее корректный код всегда должен передавать переменную в bindParam или использовать bindValue/execute.

В: Можно ли использовать bindParam в цикле?
О: Да. bindParam полезен, если вы хотите менять значение переменной и вызывать execute() многократно (переменная будет прочитана при каждом execute()).

В: Что безопаснее — bindValue или execute([…])?
О: Оба безопасны, т.к. параметры передаются отдельно от SQL. execute([…]) — самый компактный и часто используемый способ; bindValue полезен, если хотите явно указать тип (PDO::PARAM_INT и т.д.).

В: Как экранировать % и _ в LIKE?
О: Замените % и _ на ‘%’ и ‘_’ (и корректно экранируйте слэши), либо используйте ESCAPE в SQL.


Источники


Заключение

Коротко: проблема в том, что bindParam привязывает переменную по ссылке, а передача выражения “$name%” в качестве аргумента даёт фатал в PHP 8.4. Самые простые исправления — использовать bindValue(), сформировать переменную $like = $name . ‘%’ и привязать её через bindParam, или передать значение напрямую в execute([‘:name’ => $name . ‘%’]). Не забудьте включить PDO::ERRMODE_EXCEPTION для нормальной отладки и следить за настройкой PDO::ATTR_EMULATE_PREPARES. Эти шаги решат 500‑ошибку и восстановят корректный поиск по LIKE в вашей сортировке брендов.

Авторы
Проверено модерацией
Модерация
Ошибка 500 PDO LIKE bindParam в PHP 8.4: как исправить