Экстренный рефакторинг 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);    }}?>

 

 

 

ссылка на оригинал статьи https://habr.com/ru/articles/1098930/