Экстренный рефакторинг 32 проектов с mysql_* на PHP 8.5 без единой строчки правок в бизнес-логике

Вечером 21 октября 2026 года мой телефон разорвался от звонков. В рабочем чате, где обычно обсуждают котов и сроки сдачи макетов, висел одинокий, но очень жирный алерт от моего сканера безопасности. Который гласил, что нам всем конец.
Две критические уязвимости в PHP, раскрытые утром этого злополучного дня. Точных деталей в паблике еще небыло. Говорилось, что вендор уже выкатил фикс в 8.5.12, а все, что ниже — официально в красной зоне. Такая информация была на момент осознания масштаба проблемы.
У меня на поддержке около сотни сайтов. Разброс по серверам — от убитого shared-хостинга, где админ спит еще с нулевых, до пары десятков VPS. Версии PHP — от гордого 8.3 на свежих проектах до музейных экспонатов.
Первая мысль была рациональной: «Завтра с утра спокойно пропишем обновление в CI/CD, прогоняем тесты, выкатываем». Вторая мысль, более честная, пришла через секунду: «если дыры опубликовали сегодня, эксплойты появятся в паблике завтра к обеду». Спать я в ту ночь лег в 04:30.
Что за уязвимости и почему именно 8.5.12
Деталей CVE на момент алерта не было. Точнее не было у меня. Было другое: PHP Group четко написала в релизе — апдейт критический, рекомендуется немедленное обновление.
В 2026 году PHP 8.5 — это уже вполне рабочий мейнстрим, но в продакшене он стоит далеко не везде. Многие клиенты сидят на 8.2 или 8.3, потому что «работает — не трогай». А у меня в заначке были те самые 32 сайта, которые до сих пор дышали на ладан под PHP 5.4 и 5.6.
Это старые самописные CRM, интернет-магазины на Joomla 1.5 и форумы на vBulletin, которые клиенты отказываются переписывать, потому что «там СТОЛЬКО всего за десять лет».
Вариант «просто обновиться до 8.5» для них означал мгновенную белую страницу. Расширение mysql_* вырезали из ядра еще в PHP 7.0. Любой вызов mysql_connect() или mysql_query() на восьмерке роняет скрипт с фаталом.
Я молчу про стрикт-типизацию и многое другое, что у 8+ под капотом.
Марафон переноса: сто сайтов за трое суток
План был простой и жестокий.
Shared-хостинги, где у меня нет рута — тикеты в поддержку с криками, слезами или угрозами - «срочно накатите 8.5.12». Выжили не все, два провайдера ответили, что у них в планах апгрейд на декабрь. Пришлось экстренно поднимать для них контейнеры на своих VPS и перекидывать DNS.
Свои VPS. Тут полегче:
apt update && apt install php8.5-cli php8.5-fpm. Но дьявол, как обычно, сидел вphp.iniиextension=.
А еще:
Отвалились старые
IonCube Loaderдля PHP 7.4, которые зачем-то тянули старые билды WordPress и Ктулху его сзнает чего еще.Сломался
mcryptна одном древнем биллинге (пришлось в три часа ночи писать враппер вокругopenssl).Composer на паре проектов сошел с ума от того, что в
composer.jsonжестко прибито"php": ">=5.6, <8.0". Пришлось массово резать эти строки черезsed.Deprecated-предупреждения от
create_function()и неявных приведений типов забили логи так, что ротация не справлялась.
Из сотни сайтов 68 поднялись на 8.5.12 после простой смены версии в FPM и легкой правки конфигов, ну и пары исправлений в самих скриптах. Это были относительно свежие WordPress, Laravel, OpenCart и самописы. Я прогонял их через grep -R "mysql_", чтобы уж убедиться окончательно. — чисто.
Оставшиеся 32 проекта при переключении на восьмерку выдавали Fatal error: Uncallable function mysql_connect() и много-много чего еще…
Почему нельзя «просто переписать»
mysql_* — это проклятие легаси. В этих 32 проектах функции mysql_connect, mysql_query, mysql_fetch_assoc размазаны по сотням файлов. Где-то они обернуты в свои классы, где-то вызываются напрямую в процедурном лапше-коде.
Переписать это на PDO или хотя бы mysqli за неделю? Математически невозможно. В одном только проекте старой логистической компании было 400+ вхождений mysql_query. Даже если тратить на замену одной функции с тестированием две минуты (что очень оптимистично, ага), это 800 минуты чистого времени. Почти 14 часов на один проект. А их 32. И это не считая переписывания логики обработки ошибок, так как PDO по умолчанию кидает исключения, а старый код ждет, что mysql_query() просто молча вернет false.
Даунтайм на две недели, пока я буду в одно лицо разгребать этот спагетти-код, клиент не оплатит. А оставлять их на PHP 5.6, когда в сети уже крутят PoC для свежих CVE — это подписать акт о профессиональном самоубийстве.
А значит, нужно было решение, которое позволит запустить старый код на новой версии без правок самого кода.
Анатомия «таблетки»: mysql-tablet.php
Я не первый, кто столкнулся с этой проблемой, но готовые врапперы вроде mysqlnd-uh или mysqli_compat часто тянут лишнюю магию, которая на PHP 8.5 ведет себя непредсказуемо.
Мне нужен был максимально тупой, прозрачный слой. Один файл, который инклудится в auto_prepend_file в php.ini или .user.ini, ну или вообще просто в скрипт.
Я назвал его mysql-tablet.php. Идея простая: проверить, не объявлены ли функции mysql_* (вдруг хостинг чудит и оставил их для обратной совместимости), и если нет — объявить их поверх mysqli.
Вот как это работает под капотом.
1. Затыкаем исключения и определяем константы
Легаси-код 2012 года не знает про try-catch. Если mysqli при соединении словит ошибку, он по умолчанию бросит исключение. Старый if (!$link) просто не сработает, скрипт умрет некрасиво.
if (function_exists('mysqli_report') && defined('MYSQLI_REPORT_OFF')) {
mysqli_report(MYSQLI_REPORT_OFF);
}Эта строка переводит mysqli в тихий режим. Теперь он ведет себя как старый mysql: возвращает false и пишет ошибку в свой внутренний лог, а не роняет скрипт.
Дальше — константы типов выборки. В коде везде написано mysql_fetch_array($res, MYSQL_ASSOC). В PHP 8.5 эти константы не определены глобально, если не включено расширение mysql. Пришлось объявить их вручную, маппя на MYSQLI_*:
if (!defined('MYSQL_ASSOC')) {
define('MYSQL_ASSOC', defined('MYSQLI_ASSOC') ? MYSQLI_ASSOC : 1);
}2. Магия mysql_connect
Функция mysql_connect() исторически умеет принимать не только localhost, но и hostname:3306, :/tmp/mysql.sock и даже p:hostname для персистентных соединений. Мой слой должен это распарсить и правильно передать в mysqli_real_connect.
function mysql_connect($server, $username, $password, $new_link = false, $client_flags = 0)
{
try {
if (!function_exists('mysqli_init')) {
return false;
}
$host = (string) $server;
$port = null;
$socket = null;
$persistentPrefix = '';
if (str_starts_with($host, 'p:')) {
$persistentPrefix = 'p:';
$host = substr($host, 2);
}
if ($host === '') {
$host = 'localhost';
} elseif (str_starts_with($host, ':')) {
$socket = substr($host, 1);
$host = 'localhost';
} elseif (str_contains($host, ':')) {
[$hostname, $extra] = explode(':', $host, 2);
if ($hostname !== '') {
$host = $hostname;
}
if ($extra !== '') {
if ($extra[0] === '/') {
$socket = $extra;
} else {
$port = (int) $extra;
}
}
}
$host = $persistentPrefix . $host;
$link = mysqli_init();
if (!$link instanceof mysqli) {
return false;
}
$ok = @mysqli_real_connect(
$link,
$host,
(string) $username,
(string) $password,
null,
$port,
$socket,
(int) $client_flags
);
if (!$ok) {
$GLOBALS['mysql_oldstyle_connect_errno'] = function_exists('mysqli_connect_errno')
? mysqli_connect_errno()
: 0;
$GLOBALS['mysql_oldstyle_connect_error'] = function_exists('mysqli_connect_error')
? mysqli_connect_error()
: '';
return false;
}
$GLOBALS['mysql_oldstyle_link'] = $link;
$GLOBALS['mysql_oldstyle_connect_errno'] = 0;
$GLOBALS['mysql_oldstyle_connect_error'] = '';
return $link;
} catch (Throwable $e) {
$GLOBALS['mysql_oldstyle_connect_errno'] = (int) $e->getCode();
$GLOBALS['mysql_oldstyle_connect_error'] = $e->getMessage();
return false;
}
} Почему линк кладется в $GLOBALS['mysql_oldstyle_link']? Потому что в старом коде часто опускают параметр $link в последующих функциях, полагаясь на последнее открытое соединение. Глобал — грязно, но это единственный способ заставить процедурный код 15-летней давности работать без явной передачи хендла.
3. Fetch-функции и строгая проверка на false
Это самый тонкий момент, на котором ломается большая половина легаси. В старом mysql_fetch_assoc() при достижении конца выборки возвращался false. В mysqli_fetch_assoc() при пустом результате возвращается null.
Код вида while ($row = mysql_fetch_assoc($res)) { ... } на null работает. Но код вида if ($row === false) { /* какая-то спец-логика */ } на null падает.
Поэтому в таблетке я принудительно нормализую результат:
function mysql_fetch_assoc($res) {
if (!$res instanceof mysqli_result) {
return false;
}
$row = mysqli_fetch_assoc($res);
return $row === null ? false : $row;
}То же самое сделано для mysql_fetch_row и mysql_fetch_array. Мы обманываем старый код, возвращая ему привычный false вместо null.
4. Ошибки соединения
Если mysql_connect() упал, старый код полезет в mysql_errno() и mysql_error(). Но если мы не сохранили ошибку в глобальные переменные, эти функции вернут пустоту.
if (!$ok) {
$GLOBALS['mysql_oldstyle_connect_errno'] = function_exists('mysqli_connect_errno') ? mysqli_connect_errno() : 0;
$GLOBALS['mysql_oldstyle_connect_error'] = function_exists('mysqli_connect_error') ? mysqli_connect_error() : ''; return false;
}А сами функции mysql_errno() и mysql_error() теперь просто читают эти глобалы, если им не передали конкретный линк.
Грабли и честные ограничения
Сразу предупреждаю: mysql-tablet.php — это не панацея. Это морфий, который позволяет дотащить раненого до операционной, а не вырастить ему новую ногу.
Обратить внимание: Если в коде открыто два соединения через
mysql_connect()и функции вызываются без явного указания линка, таблетка будет работать с последним открытым. В 95% случаев легаси использует один линк, но я уже словил пару экзотических систем, где это ломалось.
Еще нужно обратить внимание на:
Поведение ошибок. Мы выключили исключения в
mysqli, но самиmysql_*функции внутри таблетки все равно могут возвращатьfalseтам, где оригинал на PHP 5.6 мог вернуть ресурс с предупреждением. Логи FPM в первую ночь выглядели как новогодняя елка.Безопасность. Самое важное. Эта таблетка НЕ лечит SQL-инъекции. Если в том самом коде 2012 года переменная
$idиз$_GETбез фильтрации вставляется вmysql_query("SELECT * FROM users WHERE id = $id"), она останется уязвимой. Мы просто перенесли дыру на новый интерпретатор.Производительность. Микросекунды накладных расходов на враппер. На сотне сайтов это незаметно, но под высокой нагрузкой я бы десять раз подумал.
И так. Сайты живут. Уязвимости закрыты. У меня есть 6–12 месяцев относительно спокойной жизни, чтобы в каждом из этих 32 проектов выпилить mysql-tablet.php и перевести код, хотя бы, на mysqli с подготовленными выражениями. Буду делать это спринтами. Скорее всего, по одному-два проекту в месяц.
Что в итоге
На календаре 24 октября. Все ~100 сайтов работают на PHP 8.5.12. 32 легаси-зомби поднялись на таблетке за 20 минут деплоя.
Почти во всех проектах, были файлы начинающиеся с <? , а не с <?php. Странно. И ведь я сам это, в большинстве случаев, писал и даже не обращал на это внимания. А на PHP 8.5.12 это важно.
Конечно же null, которым PHP 8+ постоянно давиться. Благо, таких мест было не много и они были исправлены ручками за короткое время. Но, пару седых волос, мне они прибавили все же.
И конечно же count(), который встретился несколько раз, но тоже был подперт костылем из ?? [] и оставлен впокое.
В целом, все прошло, хоть и нервозно, но достаточно стабильно. Я ожидал намного большего ужаса и мрака, чем оказалось в реальности.
Чего и вам желаю!
Ну собственно, вот полный код Таблетки:
<?php
/**
* mysql-tablet.php — слой совместимости для легаси-функций mysql_*.
*
* Восстанавливает API удалённого в PHP 7.0 расширения ext/mysql,
* реализуя его поверх mysqli. Позволяет запускать старый код без правок
* на PHP 8.x, включая 8.5.
*
* Требования окружения:
* - PHP >= 8.0 (используются str_starts_with() и str_contains());
* - включённое расширение mysqli.
*
* Побочные эффекты при подключении файла:
* - отключается режим исключений mysqli (MYSQLI_REPORT_OFF);
* - объявляются константы MYSQL_ASSOC, MYSQL_NUM, MYSQL_BOTH;
* - объявляются функции mysql_*, если они отсутствуют в ядре.
*
* ВНИМАНИЕ: файл является «костылём» для легаси-кода и не защищает
* от SQL-инъекций, уже присутствующих в этом коде.
*
* @package MysqlTablet
* @author Сергей Абрамов и Макс-канал "Веб Мастерская"
* @version 1.0.0
* @see https://www.php.net/manual/ru/book.mysqli.php
* @see https://www.php.net/manual/ru/book.mysql.php
*/
/**
* Отключает выброс исключений mysqli.
*
* Легаси-код ожидает «тихий» возврат false и ручную проверку ошибок
* через mysql_errno() / mysql_error(), поэтому режим исключений
* (включён по умолчанию в PHP 8.1+) необходимо погасить.
*/
if (function_exists('mysqli_report') && defined('MYSQLI_REPORT_OFF')) {
mysqli_report(MYSQLI_REPORT_OFF);
}
/**
* Эмулирует константу ext/mysql MYSQL_ASSOC (ассоциативная выборка).
*
* Значение берётся из MYSQLI_ASSOC; при его отсутствии используется
* историческое числовое значение 1.
*/
if (!defined('MYSQL_ASSOC')) {
define('MYSQL_ASSOC', defined('MYSQLI_ASSOC') ? MYSQLI_ASSOC : 1);
}
/**
* Эмулирует константу ext/mysql MYSQL_NUM (нумерованная выборка).
*
* Значение берётся из MYSQLI_NUM; при его отсутствии используется
* историческое числовое значение 2.
*/
if (!defined('MYSQL_NUM')) {
define('MYSQL_NUM', defined('MYSQLI_NUM') ? MYSQLI_NUM : 2);
}
/**
* Эмулирует константу ext/mysql MYSQL_BOTH (ассоциативная + нумерованная выборка).
*
* Значение берётся из MYSQLI_BOTH; при его отсутствии используется
* историческое числовое значение 3.
*/
if (!defined('MYSQL_BOTH')) {
define('MYSQL_BOTH', defined('MYSQLI_BOTH') ? MYSQLI_BOTH : 3);
}
/**
* Блок объявления функций-заглушек.
*
* Функции объявляются только при отсутствии одноимённых в ядре
* (то есть на PHP >= 7.0, где ext/mysql удалено). Проверка
* function_exists() защищает от фатальной ошибки повторного объявления.
*/
if (!function_exists('mysql_connect')) {
/**
* Открывает соединение с MySQL-сервером (эмуляция mysql_connect()).
*
* Поддерживает исторические форматы строки сервера:
* - «host:port» — явное указание порта;
* - «:/path/to/socket» или «host:/path/to/socket» — UNIX-сокет;
* - префикс «p:» — персистентное соединение.
*
* Успешное соединение сохраняется в $GLOBALS['mysql_oldstyle_link']
* и используется остальными функциями как соединение по умолчанию.
* Ошибки соединения фиксируются в $GLOBALS['mysql_oldstyle_connect_errno']
* и $GLOBALS['mysql_oldstyle_connect_error'] и доступны через
* mysql_errno() / mysql_error(). Исключения наружу не пробрасываются.
*
* @param string $server Хост сервера, опционально «:порт», «:/путь/к/сокету» и/или префикс «p:».
* @param string $username Имя пользователя MySQL.
* @param string $password Пароль пользователя MySQL.
* @param bool $new_link Не используется; сохранён исключительно для совместимости сигнатуры.
* @param int $client_flags Битовая маска клиентских флагов mysqli (MYSQLI_CLIENT_*).
*
* @return mysqli|false Объект соединения либо false при ошибке подключения.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
* @global int $mysql_oldstyle_connect_errno Код последней ошибки соединения ($GLOBALS['mysql_oldstyle_connect_errno']).
* @global string $mysql_oldstyle_connect_error Текст последней ошибки соединения ($GLOBALS['mysql_oldstyle_connect_error']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_real_connect()
*/
function mysql_connect($server, $username, $password, $new_link = false, $client_flags = 0)
{
try {
if (!function_exists('mysqli_init')) {
return false;
}
$host = (string) $server;
$port = null;
$socket = null;
$persistentPrefix = '';
// Префикс «p:» означает персистентное соединение в mysqli.
if (str_starts_with($host, 'p:')) {
$persistentPrefix = 'p:';
$host = substr($host, 2);
}
// Разбор исторических форматов строки сервера ext/mysql.
if ($host === '') {
$host = 'localhost';
} elseif (str_starts_with($host, ':')) {
$socket = substr($host, 1);
$host = 'localhost';
} elseif (str_contains($host, ':')) {
[$hostname, $extra] = explode(':', $host, 2);
if ($hostname !== '') {
$host = $hostname;
}
if ($extra !== '') {
if ($extra[0] === '/') {
$socket = $extra; // Всё, что начинается с «/», — путь к сокету.
} else {
$port = (int) $extra; // Иначе — номер порта.
}
}
}
$host = $persistentPrefix . $host;
$link = mysqli_init();
if (!$link instanceof mysqli) {
return false;
}
// «@» глушит warning'и: легаси само проверяет ошибки через mysql_errno().
$ok = @mysqli_real_connect(
$link,
$host,
(string) $username,
(string) $password,
null,
$port,
$socket,
(int) $client_flags
);
if (!$ok) {
$GLOBALS['mysql_oldstyle_connect_errno'] = function_exists('mysqli_connect_errno')
? mysqli_connect_errno()
: 0;
$GLOBALS['mysql_oldstyle_connect_error'] = function_exists('mysqli_connect_error')
? mysqli_connect_error()
: '';
return false;
}
$GLOBALS['mysql_oldstyle_link'] = $link;
$GLOBALS['mysql_oldstyle_connect_errno'] = 0;
$GLOBALS['mysql_oldstyle_connect_error'] = '';
return $link;
} catch (Throwable $e) {
$GLOBALS['mysql_oldstyle_connect_errno'] = (int) $e->getCode();
$GLOBALS['mysql_oldstyle_connect_error'] = $e->getMessage();
return false;
}
}
/**
* Выбирает базу данных по умолчанию для соединения (эмуляция mysql_select_db()).
*
* @param string $database_name Имя базы данных.
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return bool true при успехе, false при ошибке или невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_select_db()
*/
function mysql_select_db($database_name, $link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return false;
}
return mysqli_select_db($link, (string) $database_name);
}
/**
* Выполняет SQL-запрос (эмуляция mysql_query()).
*
* @param string $sql Текст SQL-запроса.
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return mysqli_result|bool Объект результата для SELECT-подобных запросов,
* true для успешных запросов без результата,
* false при ошибке или невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_query()
*/
function mysql_query($sql, $link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return false;
}
return mysqli_query($link, (string) $sql);
}
/**
* Выбирает следующую строку результата в виде нумерованного массива (эмуляция mysql_fetch_row()).
*
* В отличие от mysqli_fetch_row() возвращает false вместо null при выходе
* за конец результата — именно на такое поведение рассчитан легаси-код.
*
* @param mysqli_result|mixed $res Результат запроса, возвращённый mysql_query().
*
* @return array<int, string|null>|false Строка результата либо false, если строк больше нет
* или передан не-результат.
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_fetch_row()
*/
function mysql_fetch_row($res)
{
if (!$res instanceof mysqli_result) {
return false;
}
$row = mysqli_fetch_row($res);
return $row === null ? false : $row;
}
/**
* Выбирает следующую строку результата в виде ассоциативного массива (эмуляция mysql_fetch_assoc()).
*
* В отличие от mysqli_fetch_assoc() возвращает false вместо null при выходе
* за конец результата.
*
* @param mysqli_result|mixed $res Результат запроса, возвращённый mysql_query().
*
* @return array<string, string|null>|false Строка результата либо false, если строк больше нет
* или передан не-результат.
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_fetch_assoc()
*/
function mysql_fetch_assoc($res)
{
if (!$res instanceof mysqli_result) {
return false;
}
$row = mysqli_fetch_assoc($res);
return $row === null ? false : $row;
}
/**
* Выбирает следующую строку результата в заданном формате (эмуляция mysql_fetch_array()).
*
* Исторические константы MYSQL_ASSOC / MYSQL_NUM / MYSQL_BOTH прозрачно
* отображаются на MYSQLI_ASSOC / MYSQLI_NUM / MYSQLI_BOTH.
* Возвращает false вместо null при выходе за конец результата.
*
* @param mysqli_result|mixed $res Результат запроса, возвращённый mysql_query().
* @param int|null $result_type Одна из констант MYSQL_ASSOC, MYSQL_NUM, MYSQL_BOTH;
* null эквивалентен MYSQL_BOTH.
*
* @return array<int|string, string|null>|false Строка результата либо false, если строк больше нет
* или передан не-результат.
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_fetch_array()
*/
function mysql_fetch_array($res, $result_type = null)
{
if (!$res instanceof mysqli_result) {
return false;
}
if ($result_type === null) {
$result_type = MYSQL_BOTH;
}
$result_type = (int) $result_type;
// Маппинг исторических констант ext/mysql на константы mysqli.
$map = [
MYSQL_ASSOC => MYSQLI_ASSOC,
MYSQL_NUM => MYSQLI_NUM,
MYSQL_BOTH => MYSQLI_BOTH,
];
$mode = $map[$result_type] ?? MYSQLI_BOTH;
$row = mysqli_fetch_array($res, $mode);
return $row === null ? false : $row;
}
/**
* Выбирает следующую строку результата в виде объекта (эмуляция mysql_fetch_object()).
*
* Возвращает false вместо null при выходе за конец результата.
* Ошибки создания экземпляра класса перехватываются и также приводят к false.
*
* @param mysqli_result|mixed $res Результат запроса, возвращённый mysql_query().
* @param string $classname Имя класса создаваемого объекта.
* @param array<int, mixed> $params Аргументы конструктора класса.
*
* @return object|false Экземпляр $classname с полями строки либо false, если строк больше нет,
* передан не-результат или не удалось создать объект.
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_fetch_object()
*/
function mysql_fetch_object($res, $classname = 'stdClass', $params = [])
{
if (!$res instanceof mysqli_result) {
return false;
}
try {
$object = mysqli_fetch_object(
$res,
(string) $classname,
(array) $params
);
} catch (Throwable $e) {
return false;
}
return $object === null ? false : $object;
}
/**
* Возвращает число строк, затронутых последним запросом (эмуляция mysql_affected_rows()).
*
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return int Число затронутых строк; -1 при ошибке запроса или невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_affected_rows()
*/
function mysql_affected_rows($link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return -1;
}
return mysqli_affected_rows($link);
}
/**
* Возвращает ID строки, вставленной последним запросом (эмуляция mysql_insert_id()).
*
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return int|string ID последней вставленной строки (string для значений BIGINT);
* 0 при невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_insert_id()
*/
function mysql_insert_id($link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return 0;
}
return mysqli_insert_id($link);
}
/**
* Возвращает код последней ошибки (эмуляция mysql_errno()).
*
* Если соединение отсутствует или невалидно, отдаёт код ошибки соединения,
* сохранённый функцией mysql_connect() в $GLOBALS.
*
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return int Код последней ошибки; 0, если ошибок не было.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
* @global int $mysql_oldstyle_connect_errno Код последней ошибки соединения ($GLOBALS['mysql_oldstyle_connect_errno']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_errno()
*/
function mysql_errno($link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return (int) ($GLOBALS['mysql_oldstyle_connect_errno'] ?? 0);
}
return mysqli_errno($link);
}
/**
* Возвращает текст последней ошибки (эмуляция mysql_error()).
*
* Если соединение отсутствует или невалидно, отдаёт текст ошибки соединения,
* сохранённый функцией mysql_connect() в $GLOBALS.
*
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return string Текст последней ошибки; пустая строка, если ошибок не было.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
* @global string $mysql_oldstyle_connect_error Текст последней ошибки соединения ($GLOBALS['mysql_oldstyle_connect_error']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_error()
*/
function mysql_error($link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return (string) ($GLOBALS['mysql_oldstyle_connect_error'] ?? '');
}
return mysqli_error($link);
}
/**
* Возвращает число строк в результате запроса (эмуляция mysql_num_rows()).
*
* @param mysqli_result|mixed $res Результат запроса, возвращённый mysql_query().
*
* @return int|false Число строк в результате либо false, если передан не-результат.
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_num_rows()
*/
function mysql_num_rows($res)
{
if (!$res instanceof mysqli_result) {
return false;
}
return mysqli_num_rows($res);
}
/**
* Освобождает память, занятую результатом запроса (эмуляция mysql_free_result()).
*
* @param mysqli_result|mixed $res Результат запроса, возвращённый mysql_query().
*
* @return bool true, если память освобождена; false, если передан не-результат.
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_free_result()
*/
function mysql_free_result($res)
{
if (!$res instanceof mysqli_result) {
return false;
}
mysqli_free_result($res);
return true;
}
/**
* Закрывает соединение с сервером (эмуляция mysql_close()).
*
* Если закрывается соединение по умолчанию, оно дополнительно удаляется
* из $GLOBALS, чтобы остальные функции не использовали «мёртвый» линк.
* Закрытие невалидного соединения считается успешным (true), как в ext/mysql.
*
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return bool true при успешном закрытии (или невалидном соединении), false при ошибке.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_close()
*/
function mysql_close($link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return true;
}
$result = mysqli_close($link);
if (isset($GLOBALS['mysql_oldstyle_link']) && $GLOBALS['mysql_oldstyle_link'] === $link) {
unset($GLOBALS['mysql_oldstyle_link']);
}
return $result;
}
/**
* Экранирует специальные символы в строке для использования в SQL (эмуляция mysql_real_escape_string()).
*
* @param string $sql Исходная строка.
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return string|false Экранированная строка либо false при невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте подготовленные выражения mysqli/PDO.
* @see mysqli_real_escape_string()
*/
function mysql_real_escape_string($sql, $link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return false;
}
return mysqli_real_escape_string($link, (string) $sql);
}
/**
* Возвращает версию MySQL-сервера (эмуляция mysql_get_server_info()).
*
* @param mysqli|null $link Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return string|false Строка версии сервера либо false при невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_get_server_info()
*/
function mysql_get_server_info($link = null)
{
if ($link === null) {
$link = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link instanceof mysqli) {
return false;
}
return mysqli_get_server_info($link);
}
/**
* Устанавливает кодировку соединения (эмуляция mysql_set_charset()).
*
* @param string $charset Имя кодировки (например, «utf8mb4»).
* @param mysqli|null $link_identifier Соединение; null — соединение по умолчанию из $GLOBALS.
*
* @return bool true при успехе, false при ошибке или невалидном соединении.
*
* @global mysqli|null $mysql_oldstyle_link Соединение по умолчанию ($GLOBALS['mysql_oldstyle_link']).
*
* @deprecated Только для совместимости с легаси; в новом коде используйте mysqli или PDO.
* @see mysqli_set_charset()
*/
function mysql_set_charset($charset, $link_identifier = null)
{
if ($link_identifier === null) {
$link_identifier = $GLOBALS['mysql_oldstyle_link'] ?? null;
}
if (!$link_identifier instanceof mysqli) {
return false;
}
return mysqli_set_charset($link_identifier, (string) $charset);
}
}
?>KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.