PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 106 · Миграция после первого знакомства с PHP и SQL
Старый mysql_*: соединение, кодировка, запросы и перенос на MySQLi
Прочитать исторический договор без запуска PHP5 и перенести каталог на явное соединение, utf8mb4, обработку ошибок и понятный результат записи.
Зачем читать старый API и что должно сохраниться
Вы открыли старый PHP-проект и увидели mysql_connect, mysql_query или mysql_result. Задача — восстановить смысл программы и перенести его на поддерживаемый драйвер: тот же каталог, те же сохранённые значения и понятные отказы. Здесь старый договор описан полностью в отдельных карточках; новые программы используют MySQLi.
- PHP-код выбирает драйвер: ext/mysql раньше, MySQLi сейчас
- Соединение с сервером и выбор схемы
- SQL выбирает id/name из таблицы товаров
- Результат запроса — отдельный набор
- fetch извлекает очередную PHP-строку
- Явное HTML-экранирование → видимая таблица
Если MySQL для вас новая тема, начните с соединения, сервера и схемы и таблиц и связей. Здесь текущий новый шаг — различия API при переносе существующего кода; знание старого расширения не требуется.
| Что было в старом API | Что меняется при переносе |
|---|---|
| mysql_query($sql,$link) | mysqli_query($db,$sql): соединение стало первым обязательным аргументом. |
| link и result — resource | mysqli и mysqli_result — разные объекты с разными обязанностями. |
| Последний link и попытка автоматического подключения | Явно передавать нужный $db и выбирать схему. |
| fetch возвращал false на конце | Современный fetch возвращает null на конце; отдельно возможна ошибка false. |
| if(!$result) / die(mysql_error()) | Различать пустой результат, bool записи и исключение; не раскрывать детали посетителю. |
| mysql_result($r,позиция,поле) | Получить строку или сделать точный WHERE; официальной функции mysqli_result() нет. |
MySQLi допускает процедурные вызовы и методы объекта: mysqli_query($db,$sql) и $db->query($sql) работают с тем же соединением. Не требуется переписывать весь проект на классы ради миграции; требуется проверить параметры, возвраты, ошибки и владение результатом. PDO_MySQL — отдельный современный путь; его подключение и подготовленные запросы рассматриваются отдельно.
Практическая ситуация: Find/replace mysql_ to mysqli_ makes code nonfunctional. Автор заменил префиксы во всём коде: редактор не видел синтаксических ошибок, но обращения к базе перестали работать. Разбор показывает явное соединение и изменение порядка аргументов вместо массовой замены имён. Вопрос используется как описание проблемы; код и объяснение написаны заново, технические правила сверены с PHP.net.
Версии: где заканчивается исторический договор
| Версия | Историческое изменение |
|---|---|
| PHP4 | Исходное расширение обычно включалось по умолчанию с клиентской библиотекой. |
| PHP4.3.0 | mysql_escape_string устарела: не учитывает charset подключения. Появились client_encoding, real_escape_string, info. |
| PHP5.2.3 | Добавлена mysql_set_charset; исторически требовался MySQL5.0.7+. |
| PHP5.3.0 | Доступен mysqlnd; mysql_db_query устарела. |
| PHP5.4.0 | mysqlnd стал стандартным вариантом клиентской библиотеки. |
| PHP5.5.0 | Устарело всё ext/mysql. |
| PHP7.0.0 | ext/mysql удалено; mysql_* более не входит в PHP. |
| PHP8.1+ MySQLi | Ошибки по умолчанию обрабатываются MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT; в примерах режим задан явно. |
Общий официальный changelog ext/mysql отдельно фиксирует удаление в7.0. Частные начальные версии и ранние deprecation указаны у карточек. Мы не объявляем исторические примеры выполненными на PHP8. Проверяется современный перенос, а исторический контракт сверяется с сохранённой официальной документацией.
Подготовить современный перенос и запустить один архив
Для новых программ нужны сервер MySQL8.4, PHP8.2+ с mysqli/mysqlnd, своя учебная схема и ограниченный пользователь. Проверочный стенд — PHP8.5.11/MySQL8.4.13LTS. Сервер и база не упакованы в архив; каждый пример сам создаёт TEMPORARY-таблицу своего соединения.
- Подготовить локальный сервер и пользователя по полной инструкции с CREATE DATABASE/USER/GRANT. Нужны SELECT/INSERT/UPDATE/DELETE/CREATE TEMPORARY TABLES только для учебной схемы.
- В терминале задать собственные PHPMAN_MYSQL_HOST, PHPMAN_MYSQL_PORT, PHPMAN_MYSQL_DATABASE, PHPMAN_MYSQL_USER, PHPMAN_MYSQL_PASSWORD. Для TCP указан127.0.0.1; порт стенда33070 — не обязательный порт вашего сервера.
- Распаковать один архив, выполнить php example.php, открыть result.html. README.txt и helper уже вложены; предыдущие программы запускать не нужно.
- result.json содержит данные таблицы; metrics.json — проверки и версии без пароля. Все новые программы — CLI, а не проверка HTTP-страниц и не демонстрация авторизации.
$env:PHPMAN_MYSQL_HOST='127.0.0.1'
$env:PHPMAN_MYSQL_PORT='33070'
$env:PHPMAN_MYSQL_DATABASE='my_training'
$env:PHPMAN_MYSQL_USER='my_student'
# PHPMAN_MYSQL_PASSWORD задайте своим локальным паролем
php example.phpВ Unix-shell те же значения задаются export PHPMAN_MYSQL_DATABASE='my_training' и аналогично для остальных переменных. При undefined function mysqli_connect проверьте расширение именно того PHP, которым запускаете программу; при undefined function mysql_connect на PHP8 исправляется код, а не устанавливается старое расширение. Не публикуйте phpinfo с конфигурацией сервера.
Сервер, PHP-расширение и клиентская библиотека
Исторический mysql.requirements требовал сборку PHP с поддержкой MySQL. Наличие сервера само по себе не добавляло PHP-функции. Старый клиентский модуль мог использовать libmysqlclient или mysqlnd; версия сервера, клиентская библиотека и версия PHP — три разных величины.
Сегодня расширение ext/mysql не является доступной зависимостью PHP7/8. Для работы архивов проверяют mysqli и mysqlnd текущего PHP, затем доступность локального MySQL и права на отдельную схему. Нельзя подмешать php_mysql.dll от PHP5 в PHP8. Пути настройки современного PHP даны в начале руководства и подготовке MySQL.
Как читать старые инструкции установки без опасного отката
| Историческая сборка | Что означала настройка |
|---|---|
| PHP4 / Unix | libmysqlclient по умолчанию; --without-mysql отключало поддержку; клиентские библиотеки поставлялись вместе с PHP. |
| PHP5.0–5.2 / Unix | --with-mysql=DIR указывал каталог headers/libraries; расширение уже не включалось автоматически, библиотеки не были bundled. |
| PHP5.3 / Unix | --with-mysql=mysqlnd выбирал встроенный драйвер; --with-mysql=DIR — libmysqlclient. |
| PHP5.4 / Unix | --with-mysql использовал mysqlnd по умолчанию; отдельный DIR позволял libmysqlclient. |
| PHP5.0–5.2 / Windows | extension_dir и php_mysql.dll в php.ini; libmysql.dll должна находиться в PATH. Копирование в системный каталог не рекомендовалось. |
| PHP5.3+ / Windows | При mysqlnd libmysql.dll более не требовалась; php_mysql.dll относилась к тогдашнему ext/mysql. |
Общий исторический --with-mysql[=DIR] относился к конфигурации сборки, DIR — к клиентским библиотекам, не к файлам базы. Расширение работало с MySQL4.1+, но не раскрывало новые возможности этих серверов; для них предназначался MySQLi. Совместная загрузка ext/mysql и mysqli требовала одной клиентской библиотеки, иначе возможны конфликты.
В официальных старых примечаниях отмечены конфликты с recode и ограничения набора charset у bundled libmysqlclient. Ошибка загрузки php_mysql.dll могла означать отсутствие самой DLL или зависимости libmysql.dll. Эти причины относятся к историческому окружению; современный перенос проверяет текущий драйвер и его настройки отдельно.
Все настройки старого mysql.* и их границы
| Историческая директива | Значение и область изменения | Смысл при чтении старого ini |
|---|---|---|
| mysql.allow_local_infile | 1; INI_SYSTEM | Разрешение локальных файлов для LOAD DATA со стороны PHP. Не включать ради миграции без отдельной задачи. |
| mysql.allow_persistent | 1; INI_SYSTEM | Разрешение постоянных соединений. |
| mysql.max_persistent | -1; INI_SYSTEM | Максимум постоянных соединений на процесс; -1 без заданного лимита. |
| mysql.max_links | -1; INI_SYSTEM | Все соединения процесса, включая постоянные. |
| mysql.trace_mode | 0; INI_ALL | С PHP4.3: диагностические предупреждения о scan, неосвобождённых результатах и SQL-ошибках. |
| mysql.default_port | NULL; INI_ALL | При отсутствии: MYSQL_TCP_PORT, mysql-tcp из /etc/services, затем compile-time MYSQL_PORT; Windows использовал последний. |
| mysql.default_socket | NULL; INI_ALL | Сокет локального сервера при неуказанном пути. |
| mysql.default_host | NULL; INI_ALL | Хост по умолчанию; исключение — старый SQL safe mode. |
| mysql.default_user | NULL; INI_ALL | Пользователь по умолчанию; исключение — SQL safe mode. |
| mysql.default_password | NULL; INI_ALL | Пароль по умолчанию; исключение — SQL safe mode. |
| mysql.connect_timeout | 60; INI_ALL | Секунды подключения; на Linux также ожидание первого ответа. |
INI_SYSTEM задавалось системной конфигурацией; INI_ALL допускало доступные пользовательские способы изменения. Директивы mysql.* принадлежат удалённому расширению, не являются автоматически настройками mysqli.*. Не переносите пароли из общего php.ini в публикуемый пример. В новых программах параметры подключения явны, а таймаут задан до real_connect.
Два исторических ресурса и два современных объекта
| Уровень | Историческое значение | Современный результат |
|---|---|---|
| Подключение | link_identifier: resource | mysqli: канал к серверу, выбранная схема и состояние. |
| Ответ SELECT | result: resource | mysqli_result: строки, поля и курсор одного запроса. |
| Успешная команда без набора | true | true; нельзя передавать в fetch/free результата. |
| Извлечённая строка | array или object | Отдельные PHP-данные, не соединение и не весь набор. |
У ext/mysql было ровно два специальных вида ресурсов: link и результат запроса. Освобождение набора и закрытие подключения — разные действия. Эта разница показана в самостоятельном сценарии владения результатами. Не используйте is_resource($db) для современного mysqli: он является объектом.
Клиентские флаги и формы результата
| Старая константа | Что выбирала | Современный смысл |
|---|---|---|
| MYSQL_CLIENT_COMPRESS | Протокол сжатия | Отдельная возможность клиентского соединения; не формат таблицы. |
| MYSQL_CLIENT_IGNORE_SPACE | Пробелы после имён SQL-функций | Меняет разбор SQL; не обрезает пробелы из данных. |
| MYSQL_CLIENT_INTERACTIVE | interactive_timeout вместо wait_timeout | Политика ожидания сервера для соединения. |
| MYSQL_CLIENT_SSL | SSL; требовалась клиентская библиотека4.x+ | Не доказательство проверки сертификата; современная настройка TLS рассматривается отдельно. |
| MYSQL_ASSOC | Ключи по именам полей | MYSQLI_ASSOC. |
| MYSQL_NUM | Индексы0,1,… | MYSQLI_NUM. |
| MYSQL_BOTH | Имена и индексы одной строки | MYSQLI_BOTH; не два разных товара. |
Старые MYSQL_* определялись только при загрузке ext/mysql; в PHP8 их нет. Флаги соединения комбинировались побитовым OR, а режим fetch выбирался одним значением. В некоторых старых connect-контрактах число128 разрешало LOCAL INFILE; не подставляйте магическое число в современный код. Префикс MYSQLI_ и значение конкретного флага проверяются по актуальному справочнику MySQLi.
Указать соединение и схему явно, затем прочитать каталог
Старый код рассчитывает на последнее открытое соединение. После переноса два модуля могут незаметно работать с разными базами. Начнём с одного канала к серверу: до select_db выбранной схемы нет; после выбора получаем три доступных товара.
- Открыть локальный канал без выбранной схемы
- SELECT DATABASE() → NULL
- Выбрать свою схему
- Создать учебный каталог
- SELECT id,name WHERE stock>0 → три строки
- fetch_assoc → Чай, Какао, Мёд
mysqli_select_db($db, $database);
$result = mysqli_query($db, 'SELECT id,name FROM products WHERE stock > 0 ORDER BY id');
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'], "\n";
}Соединение — канал и состояние клиента. Схема — именованное место таблиц на сервере. $db хранит объект канала; select_db выбирает схему для последующих неуточнённых имён таблиц. mysqli_query принимает $db первым, SQL вторым; mysql_query принимал link последним. Строки не выводятся автоматически: fetch_assoc берёт одну, while повторяет чтение, echo показывает имя.
Что должно получиться
| Шаг | Результат |
|---|---|
| До select_db | NULL |
| Выбрана учебная схема | true |
| Товары в наличии | Чай, Какао, Мёд |
Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.
Полная самостоятельная современная программа
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect(0,false,false);
try {
$before=$db->query('SELECT DATABASE() AS selected_schema')->fetch_assoc()['selected_schema'];
$selected=$db->select_db(getenv('PHPMAN_MYSQL_DATABASE'));
$after=$db->query('SELECT DATABASE() AS selected_schema')->fetch_assoc()['selected_schema'];
legacy108Catalog($db);
$result=mysqli_query($db,'SELECT id,name FROM mysql108_legacy_products WHERE stock > 0 ORDER BY id');
$found=[];
while ($record=mysqli_fetch_assoc($result)) { $found[]=$record['name']; }
mysqli_free_result($result);
$rows=[['До select_db',null],['Выбрана учебная схема',$selected && $after===getenv('PHPMAN_MYSQL_DATABASE')],['Товары в наличии',implode(', ',$found)]];
$checks=['no_initial_schema'=>$before===null,'selected_explicitly'=>$selected && $after===getenv('PHPMAN_MYSQL_DATABASE'),'catalog_read'=>$found===['Чай','Какао','Мёд']];
$runtime=legacy108Runtime($db);
} finally { $db->close(); }
legacy108Report('mysql108-connect-schemas', ['Шаг', 'Результат'], $rows, $checks, $runtime, $details);
echo "Explicit connection and schema: 3 available products\n";
Ожидаемый результат
Explicit connection and schema: 3 available products
Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.
Открыть сохранённый HTML-отчёт · Данные результата JSON
Границы и частые ошибки
Короткая запись использует уже открытое $db и таблицу products; полный архив создаёт свои mysql108_legacy_products. Если сервер недоступен, схема отсутствует или нет прав — это разные отказы. Подключение не создаёт базу. Настройка localhost/socket отличается от явного TCP 127.0.0.1.
Попробовать самостоятельно
Выберите stock=0. Решение: заменить условие на равенство; ожидается только Кофе. Не менять остальные поля SELECT.
Перенести имя с апострофом и эмодзи без ручной склейки SQL
После миграции в имени клиента появляется лишний слеш или пропадает эмодзи. Сохраним одно имя двумя путями, а сравнивать будем реально прочитанный текст, не красивый вид SQL.
- Исходный текст O’Reilly / Чай / 🍵
- utf8mb4 соединения и столбца
- Параметризованная INSERT → ID1
- Узкий экранированный literal → ID2
- SELECT → обе исходные строки совпадают
$db->set_charset('utf8mb4');
$stmt = $db->prepare('INSERT INTO names (id,name) VALUES (?,?)');
$stmt->bind_param('is', $id, $name);
$stmt->execute();set_charset согласует кодировку соединения. ? представляет значение целиком: bind_param передаёт его без ручных SQL-кавычек. В строковом literal одинарные кавычки всё ещё часть SQL; real_escape_string не добавляет их. Полная программа оставляет этот второй путь только для понимания исторического кода.
Что должно получиться
| ID | Прочитанное имя | Кодировка соединения |
|---|---|---|
| 1 | O'Reilly — Чай 🍵 | utf8mb4 |
| 2 | O'Reilly — Чай 🍵 | utf8mb4 |
Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.
Полная самостоятельная современная программа
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();
try {
$db->query('CREATE TEMPORARY TABLE mysql108_legacy_names (id INT PRIMARY KEY,name VARCHAR(80) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$name="O'Reilly — Чай 🍵";
$stmt=$db->prepare('INSERT INTO mysql108_legacy_names (id,name) VALUES (?,?)');
$id=1;$stmt->bind_param('is',$id,$name);$stmt->execute();$stmt->close();
$escaped=$db->real_escape_string($name);
$db->query("INSERT INTO mysql108_legacy_names (id,name) VALUES (2,'".$escaped."')");
$result=$db->query('SELECT id,name FROM mysql108_legacy_names ORDER BY id');$rows=[];$names=[];
while ($r=$result->fetch_assoc()) { $rows[]=[(int)$r['id'],$r['name'],$db->character_set_name()];$names[]=$r['name']; }
$result->free();
$details=['escaped_bytes'=>strlen($escaped),'sql_mode'=>$db->query('SELECT @@SESSION.sql_mode AS mode')->fetch_assoc()['mode']];
$checks=['utf8mb4'=>$db->character_set_name()==='utf8mb4','both_roundtrips'=>$names===[$name,$name],'input_not_modified'=>$name==="O'Reilly — Чай 🍵"];
$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-charset-quotes', ['ID', 'Прочитанное имя', 'Кодировка соединения'], $rows, $checks, $runtime, $details);
echo "Two text roundtrips preserved apostrophe and UTF-8\n";
Ожидаемый результат
Two text roundtrips preserved apostrophe and UTF-8
Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.
Открыть сохранённый HTML-отчёт · Данные результата JSON
Границы и частые ошибки
Основной вариант — prepared. Экранирование SQL не является HTML-экранированием, не делает безопасными имена колонок и не отменяет LIKE %/_. Демонстрация literal ограничена одинарными кавычками; режим NO_BACKSLASH_ESCAPES особенно важен при разборе старого кода с двойными кавычками. Для HTML-отчёта применяется htmlspecialchars отдельно.
Попробовать самостоятельно
Добавьте в имя обратный слеш. Решение: изменить исходный $name; prepared-вариант не требует нового SQL. Сверить обратное чтение, не текст запроса.
Различить пустой SELECT, успешную запись и ошибку запроса
Старый if (!$result) часто превращал разные ситуации в одно «ничего не найдено». В миграции нужно вернуть человеку правильное действие: пустой поиск допустим, несуществующее поле требует исправления программы.
- Пустой SELECT →0 строк, без ошибки
- UPDATE →true и1 изменение
- SELECT неизвестного поля →1054
- Следующий корректный SELECT →остаток13
try {
$result = $db->query($sql);
} catch (mysqli_sql_exception $error) {
$code = $error->getCode();
// Техническое сообщение — в закрытый журнал.
}SELECT возвращает объект набора даже при0 строк. UPDATE возвращает true; affected_rows сообщает1 изменение. Явно включённый MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT переводит SQL-ошибку в mysqli_sql_exception; getCode() даёт1054 для неизвестного поля. Состояние сохраняется в catch до следующей команды.
Что должно получиться
| Действие | Тип ответа | Значение |
|---|---|---|
| SELECT без совпадений | набор строк | 0 |
| UPDATE | true | 1 |
| Неизвестное поле | исключение | 1054 |
| Следующий SELECT | остаток товара | 13 |
Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.
Полная самостоятельная современная программа
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
$empty=$db->query('SELECT id,name FROM mysql108_legacy_products WHERE id=999');$emptyCount=$empty->num_rows;$empty->free();
$ok=$db->query('UPDATE mysql108_legacy_products SET stock=stock+1 WHERE id=1');$modified=$db->affected_rows;
$caughtCode=0;$caughtText='';
try {$db->query('SELECT column_that_does_not_exist FROM mysql108_legacy_products');}
catch(mysqli_sql_exception $e) {$caughtCode=$e->getCode();$caughtText=$e->getMessage();}
$healthy=(int)$db->query('SELECT stock FROM mysql108_legacy_products WHERE id=1')->fetch_assoc()['stock'];
$rows=[['SELECT без совпадений','набор строк',0],['UPDATE','true',$modified],['Неизвестное поле','исключение',$caughtCode],['Следующий SELECT','остаток товара',$healthy]];
$checks=['empty_is_success'=>$emptyCount===0,'write_is_true'=>$ok===true && $modified===1,'error_classified'=>$caughtCode===1054 && $caughtText!=='','connection_still_usable'=>$healthy===13];
$details=['technical_error_recorded'=>$caughtText!=='','public_policy'=>'error text stays out of the visitor message'];
$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-query-errors', ['Действие', 'Тип ответа', 'Значение'], $rows, $checks, $runtime, $details);
echo "Empty result, successful write and SQL error distinguished\n";
Ожидаемый результат
Empty result, successful write and SQL error distinguished
Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.
Открыть сохранённый HTML-отчёт · Данные результата JSON
Границы и частые ошибки
Не выводите полный текст исключения посетителю: он может содержать внутренние имена. Не подставляйте SQL в сообщение страницы. Этот сценарий проверяет SQL-отказ на существующем соединении; сетевой timeout/неверный пароль относятся к главе подготовки и диагностики.
Попробовать самостоятельно
Замените неизвестное поле на name. Решение: запрос перестанет выбрасывать исключение; обновите ожидаемую ветку, а не подавляйте exception оператором@.
Сразу сохранить ID, количество изменений и сведения UPDATE
Старая админка должна после сохранения открыть созданный товар и правильно сообщить «изменено». Сохраним результаты записи сразу, прежде чем следующий запрос заменит их.
- INSERT Чай/Кофе → первыйID1, строк2
- UPDATE stock5 тем же5 →0
- UPDATE stock7 →1
- DELETE ID2 →1
- SELECT →Чай с остатком7
$db->query($insertSql);
$id = $db->insert_id;
$changed = $db->affected_rows;
$details = $db->info;AUTO_INCREMENT выдаёт идентификатор. Вставка двух строк возвращает первый автоматически созданный ID; это не максимальный ID таблицы. affected_rows — число изменений этой команды. info содержит диагностическое пояснение, а SELECT подтверждает конечные данные.
Что должно получиться
| Действие | ID | Строк изменено / итоговый остаток |
|---|---|---|
| INSERT двух товаров | 1 | 2 |
| UPDATE прежним значением | 1 | 0 |
| UPDATE новым значением | 1 | 1 |
| DELETE второго товара | 2 | 1 |
| Остался Чай | 1 | 7 |
Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.
Полная самостоятельная современная программа
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();
try {
$db->query('CREATE TEMPORARY TABLE mysql108_legacy_changes (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(40),stock INT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$db->query("INSERT INTO mysql108_legacy_changes (name,stock) VALUES ('Чай',5),('Кофе',0)");
$firstId=$db->insert_id;$inserted=$db->affected_rows;$insertInfo=$db->info;
$db->query('UPDATE mysql108_legacy_changes SET stock=5 WHERE id=1');$same=$db->affected_rows;$sameInfo=$db->info;
$db->query('UPDATE mysql108_legacy_changes SET stock=7 WHERE id=1');$changed=$db->affected_rows;$changedInfo=$db->info;
$db->query('DELETE FROM mysql108_legacy_changes WHERE id=2');$deleted=$db->affected_rows;
$r=$db->query('SELECT id,name,stock FROM mysql108_legacy_changes ORDER BY id')->fetch_assoc();
$rows=[['INSERT двух товаров',(int)$firstId,$inserted],['UPDATE прежним значением',1,$same],['UPDATE новым значением',1,$changed],['DELETE второго товара',2,$deleted],['Остался '.$r['name'],(int)$r['id'],(int)$r['stock']]];
$checks=['first_generated_id'=>(int)$firstId===1,'inserted_two'=>$inserted===2,'unchanged_zero'=>$same===0,'modified_one'=>$changed===1,'deleted_one'=>$deleted===1,'retained_data'=>$r['name']==='Чай' && (int)$r['stock']===7,'info_available'=>is_string($sameInfo) && $sameInfo!==''];
$details=['insert_info'=>$insertInfo,'unchanged_update_info'=>$sameInfo,'changed_update_info'=>$changedInfo];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-write-outcomes', ['Действие', 'ID', 'Строк изменено / итоговый остаток'], $rows, $checks, $runtime, $details);
echo "ID1; insert2; unchanged0; modified1; delete1; stock7\n";
Ожидаемый результат
ID1; insert2; unchanged0; modified1; delete1; stock7
Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.
Открыть сохранённый HTML-отчёт · Данные результата JSON
Границы и частые ошибки
Ноль affected_rows не всегда означает «такого товара нет»: значение могло не измениться. FOUND_ROWS меняет договор. info сохраняется в metrics как наблюдаемая строка; не парсим её как стабильный прикладной протокол. Исторические REPLACE/UPSERT имеют особые счётчики, см. карточку.
Попробовать самостоятельно
Повторите UPDATE stock7. Решение: в обычном режиме ожидается0, а контрольный SELECT остаётся7.
Понять, что persistent-соединение не заменяет настройки каждого входа
В старом проекте pconnect мог хранить link дольше одного запроса. При переносе важно не принять старое состояние соединения за обязательную настройку приложения.
- Открыть p:-дескриптор
- Явно настроить utf8mb4 и схему
- Задать маркер первой операции
- Закрыть PHP-дескриптор
- Открыть следующий и снова настроить
- Прочитать маркер второй операции
$db = new mysqli('p:'.$host, $user, $password, $database, $port);
$db->set_charset('utf8mb4');
// Выполнить работу.
$db->close();Префикс p: просит MySQLi использовать постоянное соединение из пула процесса. Объект mysqli остаётся дескриптором текущей операции. После открытия мы заново выбираем схему и кодировку, задаём собственный маркер. Thread ID сохраняется лишь как наблюдение, не как обещание обязательного повторного использования.
Что должно получиться
| Шаг | Проверка | Результат |
|---|---|---|
| Первое открытие p: | utf8mb4 | закрыт PHP-дескриптор |
| Второе открытие p: | utf8mb4 | second operation |
| Схема выбрана явно | true | не полагаемся на остаточное состояние |
Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.
Полная самостоятельная современная программа
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$first=legacy108Connect(0,true);
$runtime=legacy108Runtime($first);$firstThread=$first->thread_id;
$first->query("SET @mysql108_request_marker='first request'");
$firstCharset=$first->character_set_name();$first->close();
$second=legacy108Connect(0,true);
try {
$secondThread=$second->thread_id;
$selected=$second->query('SELECT DATABASE() AS db')->fetch_assoc()['db'];
$second->query("SET @mysql108_request_marker='second operation'");
$marker=$second->query('SELECT @mysql108_request_marker AS marker')->fetch_assoc()['marker'];
$rows=[['Первое открытие p:',$firstCharset,'закрыт PHP-дескриптор'],['Второе открытие p:',$second->character_set_name(),$marker],['Схема выбрана явно',$selected===getenv('PHPMAN_MYSQL_DATABASE'),'не полагаемся на остаточное состояние']];
$checks=['both_initialized'=>$firstCharset==='utf8mb4' && $second->character_set_name()==='utf8mb4','schema_explicit'=>$selected===getenv('PHPMAN_MYSQL_DATABASE'),'marker_reinitialized'=>$marker==='second operation'];
$details=['first_thread'=>$firstThread,'second_thread'=>$secondThread,'same_server_thread_observed'=>$firstThread===$secondThread,'scope'=>'One CLI process; not a cross-request persistence test'];
} finally {$second->close();}
legacy108Report('mysql108-persistent-boundary', ['Шаг', 'Проверка', 'Результат'], $rows, $checks, $runtime, $details);
echo "Persistent handles explicitly initialized twice; no HTTP reuse claim\n";
Ожидаемый результат
Persistent handles explicitly initialized twice; no HTTP reuse claim
Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.
Открыть сохранённый HTML-отчёт · Данные результата JSON
Границы и частые ошибки
MySQLi имеет штатную очистку состояния при выдаче постоянного соединения: rollback, временные таблицы, блокировки и session-параметры. Это не причина хранить в пуле пользователя приложения. Проверяется один CLI-процесс; межзапросное поведение FPM/Apache требует отдельного HTTP-стенда. Пул может увеличить число соединений при большом числе рабочих процессов.
Попробовать самостоятельно
Сделайте второй вызов обычным, без persistent. Решение: прикладной результат должен сохраниться, а наблюдаемая идентичность серверного thread не обязана.
Прочитать поток до конца и только потом отправить следующий запрос
Нужно перенести старую последовательную выгрузку. Она не должна запрашивать общее число строк до чтения и запускать новый SQL посередине незавершённого ответа.
- SELECT четырёх товаров без полного буфера
- Строка1 →обработка, счётчик1
- Дочитать до null →счётчик4
- free результата
- Следующий COUNT(*) →4
$result = $db->query($sql, MYSQLI_USE_RESULT);
while ($row = $result->fetch_assoc()) {
echo $row['name'], "\n";
}
$result->free();MYSQLI_USE_RESULT означает последовательное чтение без полного клиентского буфера. Каждая fetch_assoc берёт следующую строку; null обозначает конец. После дочитывания и free соединение снова готово для следующей команды. Собственный счётчик увеличивается только при реально прочитанной строке.
Что должно получиться
| ID | Товар | Остаток |
|---|---|---|
| 1 | Чай | 12 |
| 2 | Кофе | 0 |
| 3 | Какао | 7 |
| 4 | Мёд | 4 |
Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.
Полная самостоятельная современная программа
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
$result=$db->query('SELECT id,name,stock FROM mysql108_legacy_products ORDER BY id',MYSQLI_USE_RESULT);
$rows=[];$ids=[];$count=0;
while (($r=$result->fetch_assoc())!==null) {
if ($r===false) {throw new RuntimeException('Fetch failed');}
$ids[]=(int)$r['id'];$rows[]=[(int)$r['id'],$r['name'],(int)$r['stock']];$count++;
}
$result->free();
$serverCount=(int)$db->query('SELECT COUNT(*) AS total FROM mysql108_legacy_products')->fetch_assoc()['total'];
$checks=['stream_in_order'=>$ids===[1,2,3,4],'counted_four'=>$count===4,'next_query_after_drain'=>$serverCount===4];
$details=['stream_rows'=>$count,'count_query_after_free'=>$serverCount,'bounded_teaching_report'=>'Four rows kept for the visible result; not a memory benchmark'];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-unbuffered-export', ['ID', 'Товар', 'Остаток'], $rows, $checks, $runtime, $details);
echo "Four streamed rows read; next query succeeded after drain\n";
Ожидаемый результат
Four streamed rows read; next query succeeded after drain
Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.
Открыть сохранённый HTML-отчёт · Данные результата JSON
Границы и частые ошибки
В этом учебном отчёте четыре строки сохранены в массив для показа. Это не потоковая обработка миллионов записей и не сравнение памяти. В рабочем экспортёре пишите очередную строку сразу в выходной файл. Произвольный data_seek несовместим с потоком; раннее num_rows0 не доказывает пустоту.
Попробовать самостоятельно
Ограничьте выборку WHERE stock>0. Решение: счётчик3; для честного сравнения COUNT используйте то же условие.
Полный указатель48 исторических функций
Каждая ссылка ведёт к отдельному историческому контракту с современной заменой и практическими сценариями. Это указатель исходного ext/mysql; актуальный API MySQLi находится в своём справочнике. Соседние главы продолжают метаданные и перечни объектов и диагностику и администрирование.
| Функция | Назначение |
|---|---|
| mysql_affected_rows | Не принять совпавшие строки за изменённые |
| mysql_client_encoding | Узнать кодировку старого соединения |
| mysql_close | Закрыть link, а не набор строк |
| mysql_connect | Открыть или повторно получить старое подключение |
| mysql_create_db | Диагностика соединения и административные операции |
| mysql_data_seek | Передвинуть курсор уже полученного буфера |
| mysql_db_name | Метаданные и перечни объектов |
| mysql_db_query | Найти скрытое изменение выбранной базы |
| mysql_drop_db | Диагностика соединения и административные операции |
| mysql_errno | Сохранить код последней ошибки до следующей операции |
| mysql_error | Сохранить текст диагностического отказа отдельно от ответа человеку |
| mysql_escape_string | Узнать ограничение экранирования без соединения |
| mysql_fetch_array | Выбрать ассоциативные и числовые ключи строки |
| mysql_fetch_assoc | Прочитать строку с понятными именами полей |
| mysql_fetch_field | Метаданные и перечни объектов |
| mysql_fetch_lengths | Измерить значения последней прочитанной строки |
| mysql_fetch_object | Понять превращение строки в свойства объекта |
| mysql_fetch_row | Прочитать строку по порядку SELECT-полей |
| mysql_field_flags | Метаданные и перечни объектов |
| mysql_field_len | Метаданные и перечни объектов |
| mysql_field_name | Метаданные и перечни объектов |
| mysql_field_seek | Метаданные и перечни объектов |
| mysql_field_table | Метаданные и перечни объектов |
| mysql_field_type | Метаданные и перечни объектов |
| mysql_free_result | Освободить память результата, сохранив соединение |
| mysql_get_client_info | Диагностика соединения и административные операции |
| mysql_get_host_info | Диагностика соединения и административные операции |
| mysql_get_proto_info | Диагностика соединения и административные операции |
| mysql_get_server_info | Диагностика соединения и административные операции |
| mysql_info | Прочитать пояснение записи, не считать его таблицей результата |
| mysql_insert_id | Сохранить созданный AUTO_INCREMENT сразу после вставки |
| mysql_list_dbs | Метаданные и перечни объектов |
| mysql_list_fields | Метаданные и перечни объектов |
| mysql_list_processes | Диагностика соединения и административные операции |
| mysql_list_tables | Метаданные и перечни объектов |
| mysql_num_fields | Посчитать выбранные колонки даже у пустого набора |
| mysql_num_rows | Посчитать строки результата, а не строки таблицы вообще |
| mysql_pconnect | Распознать постоянное соединение в старом проекте |
| mysql_ping | Диагностика соединения и административные операции |
| mysql_query | Отделить SQL-команду от полученного набора |
| mysql_real_escape_string | Разобрать старое экранирование с кодировкой link |
| mysql_result | Заменить обращение к одной ячейке без потери смысла |
| mysql_select_db | Явно определить базу для последующих запросов |
| mysql_set_charset | Согласовать кодировку клиента и сервера |
| mysql_stat | Диагностика соединения и административные операции |
| mysql_tablename | Метаданные и перечни объектов |
| mysql_thread_id | Диагностика соединения и административные операции |
| mysql_unbuffered_query | Перенести последовательное чтение большого набора |
Обзорный сценарий: от старого HTML-каталога к современному
Базовая задача старого обзорного примера — соединиться, выбрать базу, выполнить SELECT, перебрать строки, показать HTML, освободить результат и закрыть link. Современный перенос сохраняет этот путь, но меняет явное соединение, обработку ошибок и экранирование текста. Мы выбираем нужные поля вместо SELECT * и не печатаем сырой SQL-ответ в HTML.
- Первый современный каталог: соединение → схема → SELECT → перебор
- Выбрать форму строки для шаблона
- Освободить ответы, закрыть канал после работы
Все архивы самостоятельны: содержат подготовку исходных строк и полный example.php. Сохранённые HTML/JSON доступны у каждого сценария. Исторические фрагменты в карточках помечены отдельно и не выдаются за выполненные на PHP8.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
mysql_connectОткрыть или повторно получить старое подключение
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_connect(string $server=ini_get("mysql.default_host"), string $username=ini_get("mysql.default_user"), string $password=ini_get("mysql.default_password"), bool $new_link=false, int $client_flags=0): resource|falseОткрыть или повторно получить старое подключение
| Параметр | Что передавали |
|---|---|
| server | Имя сервера; допускались host:port или localhost с путём сокета. При отсутствии default_host — localhost:3306. |
| username / password | Учётные данные; значения по умолчанию из mysql.default_user/password. |
| new_link | false мог вернуть уже открытый link с теми же аргументами; true требовал новый. |
| client_flags | Битовая комбинация клиентских констант; историческое128 разрешало LOCAL INFILE. |
Возврат и отказ старого API
Ресурс соединения при успехе, false при отказе. Это ещё не выбранная таблица и не результат SELECT.
Ограничения и поведение
В старом SQL safe mode server/username/password/new_link/flags имели специальные ограничения: localhost:3306, владелец процесса, пустой пароль; остальные настройки игнорировались. Это историческое поведение, не режим PHP8.
$link = mysql_connect($host, $user, $password);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
mysqli_connect($host,$user,$password,$schema,$port) возвращает mysqli; объект передаётся первым аргументом процедурных вызовов. Не переносите ожидание автоматического повторного link.
- Каталог: явно выбрать учебную схему и получить три товара.
- Пул соединений: инициализировать каждый новый PHP-дескриптор отдельно.
Подробный современный контракт: MySQLi: Открыть или повторно получить старое подключение. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_pconnectРаспознать постоянное соединение в старом проекте
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_pconnect(string $server=ini_get("mysql.default_host"), string $username=ini_get("mysql.default_user"), string $password=ini_get("mysql.default_password"), int $client_flags=0): resource|falseРаспознать постоянное соединение в старом проекте
| Параметр | Что передавали |
|---|---|
| server | Сервер, порт или сокет, как у connect. |
| username / password | Пользователь/пароль; историческая документация описывает пользователя процесса и пустой пароль как резервные значения. |
| client_flags | Комбинация клиентских флагов; new_link у этой функции нет. |
Возврат и отказ старого API
Постоянный link либо false. Сначала искалось подходящее уже открытое соединение по серверу, пользователю и паролю.
Ограничения и поведение
Завершение скрипта и mysql_close не закрывали постоянный link. Исторический механизм имел смысл в модульном серверном PHP; лимиты процессов/соединений требовали настройки. Это не общий пул между всеми процессами.
$link = mysql_pconnect($host, $user, $password);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
В MySQLi постоянство запрашивается префиксом p: хоста. Явно задавайте schema/charset и проверяйте жизненный цикл своего SAPI; наш CLI-пример не доказывает повторное использование между HTTP-запросами.
- Повторно открыть p:-дескриптор, снова задать настройки и увидеть результат.
- Обычное непостоянное соединение для разовой выгрузки.
Подробный современный контракт: MySQLi: Распознать постоянное соединение в старом проекте. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_closeЗакрыть link, а не набор строк
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_close(resource $link_identifier=NULL): boolЗакрыть link, а не набор строк
| Параметр | Что передавали |
|---|---|
| link_identifier | Конкретный непостоянный link; без параметра — последний открытый. При отсутствии соединения — E_WARNING. |
Возврат и отказ старого API
true при успешном закрытии, false при неудаче.
Ограничения и поведение
Постоянные соединения pconnect эта функция не закрывала. Обычные links и результаты освобождались при завершении скрипта; явное закрытие завершает владение ресурсом раньше.
mysql_close($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->close() закрывает PHP-дескриптор соединения; $result->free() освобождает отдельный результат. В PHP8 mysqli_close всегда возвращает true; отказ последующей работы с закрытым объектом нельзя отследить старым ожиданием false от close.
- Освободить результат и выполнить следующий запрос тем же соединением.
- Закрыть PHP-дескриптор постоянного соединения и открыть его заново.
Подробный современный контракт: MySQLi: Закрыть link, а не набор строк. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_select_dbЯвно определить базу для последующих запросов
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_select_db(string $database_name, resource $link_identifier=NULL): boolЯвно определить базу для последующих запросов
| Параметр | Что передавали |
|---|---|
| database_name | Имя существующей доступной схемы; не SQL и не путь к файлу. |
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
true при выборе, false при отказе: например, схема отсутствует или нет прав.
Ограничения и поведение
Выбор изменял состояние соединения для последующих запросов. Он не создавал базу и не переключал другие links.
mysql_select_db($database, $link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
mysqli_select_db($db,$schema) меняет порядок аргументов относительно старого вызова. Либо укажите schema при подключении. Имя схемы нельзя подставлять вместо значения в ?.
- Показать NULL до выбора схемы и успешное чтение после.
- Различать отказ операции и успешную пустую выборку.
Подробный современный контракт: MySQLi: Явно определить базу для последующих запросов. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_client_encodingУзнать кодировку старого соединения
PHP4.3.0–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_client_encoding(resource $link_identifier=NULL): stringУзнать кодировку старого соединения
| Параметр | Что передавали |
|---|---|
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Название кодировки текущего соединения, например utf8; вызов сам ничего не перекодировал.
Ограничения и поведение
Кодировка соединения не равна автоматически кодировке PHP-файла, HTML-документа или столбца. Проверка имени не доказывает целостность всех уже сохранённых данных.
$charset = mysql_client_encoding($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->character_set_name(); для изменения — $db->set_charset("utf8mb4"). Обратное чтение русского имени и эмодзи проверяет реальный путь данных.
- Прочитать имя с апострофом и эмодзи без потерь.
- Повторить инициализацию кодировки после нового подключения.
Подробный современный контракт: MySQLi: Узнать кодировку старого соединения. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_set_charsetСогласовать кодировку клиента и сервера
PHP5.2.3–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_set_charset(string $charset, resource $link_identifier=NULL): boolСогласовать кодировку клиента и сервера
| Параметр | Что передавали |
|---|---|
| charset | Поддерживаемое сервером название кодировки. |
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
true при установке, false при отказе.
Ограничения и поведение
Появилась в PHP5.2.3; требовала MySQL5.0.7+. Это рекомендуемый исторический API вместо ручного SET NAMES: библиотека экранирования должна знать charset.
mysql_set_charset("utf8", $link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->set_charset("utf8mb4") сразу после соединения. utf8mb4 поддерживает четырёхбайтовые символы; перенос настройки не исправляет ранее повреждённые строки.
- Сохранить многоязычное имя и прочитать его тем же текстом.
- После чтения различить число UTF-8 байтов и число букв.
Подробный современный контракт: MySQLi: Согласовать кодировку клиента и сервера. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_escape_stringУзнать ограничение экранирования без соединения
PHP4.0.3–5.x; deprecated4.3.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_escape_string(string $unescaped_string): stringУзнать ограничение экранирования без соединения
| Параметр | Что передавали |
|---|---|
| unescaped_string | Исходные байты строки; аргумента подключения нет. |
Возврат и отказ старого API
Строка с экранированными специальными байтами; внешние SQL-кавычки не добавляются.
Ограничения и поведение
Не учитывала текущую кодировку соединения. Не экранировала % и _ в LIKE. Устарела уже в PHP4.3.0, раньше всего расширения. Не защищает имена таблиц и произвольный SQL.
$escaped = mysql_escape_string($name);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
Для значения предпочитайте подготовленный запрос. Когда разбираете старую склейку, MySQLi real_escape_string требует правильного charset и своего контекста SQL; универсальной «очистки ввода» нет.
- Параметризация имени с апострофом и сравнение с узким строковым literal.
- Имена выходных полей задаются фиксированными AS, не пользовательским экранированием.
Подробный современный контракт: MySQLi: Узнать ограничение экранирования без соединения. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_real_escape_stringРазобрать старое экранирование с кодировкой link
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_real_escape_string(string $unescaped_string, resource $link_identifier=NULL): string|falseРазобрать старое экранирование с кодировкой link
| Параметр | Что передавали |
|---|---|
| unescaped_string | Строка для одного SQL-литерала, не целая команда. |
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Экранированная строка, false при ошибке. Без действующего подключения также E_WARNING.
Ограничения и поведение
Учитывала charset соединения; исторически обрабатывала NUL, перевод строки, CR, обратный слеш, обе кавычки, Ctrl-Z. Не добавляла внешние кавычки и не отменяла LIKE %/_. Charset задавался сервером или set_charset, не угадывался.
$escaped = mysql_real_escape_string($name, $link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
Подготовленные параметры — основной вариант. Современное $db->real_escape_string($text) в примере используется только внутри одинарного SQL-литерала с явно заданным utf8mb4; влияние sql_mode не следует игнорировать.
- Два реальных пути сохранения одного текста с апострофом.
- Ошибка SQL обрабатывается отдельно; escaping не исправляет несуществующее имя поля.
Подробный современный контракт: MySQLi: Разобрать старое экранирование с кодировкой link. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_queryОтделить SQL-команду от полученного набора
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_query(string $query, resource $link_identifier=NULL): resource|boolОтделить SQL-команду от полученного набора
| Параметр | Что передавали |
|---|---|
| query | Одна SQL-команда, не пакет statements. Исторический справочник просил не завершать её точкой с запятой. |
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Для SELECT/SHOW/DESCRIBE/EXPLAIN — resource; для INSERT/UPDATE/DELETE — true. При отказе, в том числе недостатке прав, false.
Ограничения и поведение
Пустой SELECT остаётся успешным ресурсом. Данные надо извлечь fetch-функцией; resource не содержит готовый HTML. num_rows относится к результату, affected_rows — к записи.
$result = mysql_query($sql, $link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
mysqli_query($db,$sql) или $db->query($sql). В PHP8.1+ стандартный строгий режим MySQLi выбрасывает исключение при ошибке; задаём его явно. Пользовательские значения — в prepared.
Подробный современный контракт: MySQLi: Отделить SQL-команду от полученного набора. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_db_queryНайти скрытое изменение выбранной базы
PHP4/5; deprecated5.3.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_db_query(string $database, string $query, resource $link_identifier=NULL): resource|boolНайти скрытое изменение выбранной базы
| Параметр | Что передавали |
|---|---|
| database | Схема, которая становилась выбранной. |
| query | Одна SQL-команда. |
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Ресурс для выборки, true для успешного изменения, false при отказе.
Ограничения и поведение
Функция НЕ восстанавливала предыдущую схему после запроса. Устарела в PHP5.3.0. Это опасное для сопровождения неявное изменение контекста.
$result = mysql_db_query($database, $sql, $link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
Выбирать schema явно при соединении/через select_db; затем query. Для нескольких схем допустима фиксированная запись schema.table при соответствующих правах. Здесь пример не запрашивает доступ к чужим схемам.
- Увидеть выбранную схему как явный шаг до запроса.
- Несколько результатов используют одно понятное соединение, а не скрытые глобальные ссылки.
Подробный современный контракт: MySQLi: Найти скрытое изменение выбранной базы. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_unbuffered_queryПеренести последовательное чтение большого набора
PHP4.0.6–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_unbuffered_query(string $query, resource $link_identifier=NULL): resource|boolПеренести последовательное чтение большого набора
| Параметр | Что передавали |
|---|---|
| query | Одна SQL-команда; строки ответа заранее целиком не буферизуются. |
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Ресурс для выборки, true для успешной команды без набора, false при ошибке.
Ограничения и поведение
До полного чтения нельзя достоверно получить общее num_rows; data_seek не подходит для этого потока. Следующую команду тем же link отправляли только после чтения оставшихся строк. Это сокращает клиентский буфер, но не отменяет работу сервера.
$result = mysql_unbuffered_query($sql, $link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->query($sql,MYSQLI_USE_RESULT), последовательный fetch, затем free. Не превращайте поток обратно в огромный PHP-массив ради одной выгрузки; наша таблица всего из четырёх строк служит видимому учебному результату.
- Дочитать четыре строки и выполнить следующий SELECT.
- Для произвольной позиции выбрать buffered-результат либо SQL WHERE.
Подробный современный контракт: MySQLi: Перенести последовательное чтение большого набора. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_errnoСохранить код последней ошибки до следующей операции
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_errno(resource $link_identifier=NULL): intСохранить код последней ошибки до следующей операции
| Параметр | Что передавали |
|---|---|
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Числовой код последней ошибки либо 0, если её нет.
Ограничения и поведение
Проверять сразу после неуспешной операции: следующий вызов MySQL мог изменить диагностическое состояние. error/errno сами не затирали его. Серверный отказ не всегда становился PHP warning.
$code = mysql_errno($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
В строгом MySQLi catch(mysqli_sql_exception $e) и $e->getCode(); в нестрогом — $db->errno. Код для классификации, закрытый журнал для подробностей; не присваивайте всем отказам смысл «нет строк».
- Код1054 отличается от пустого SELECT.
- Нормальные ноль изменений и успешная запись не являются кодами SQL-ошибки.
Подробный современный контракт: MySQLi: Сохранить код последней ошибки до следующей операции. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_errorСохранить текст диагностического отказа отдельно от ответа человеку
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_error(resource $link_identifier=NULL): stringСохранить текст диагностического отказа отдельно от ответа человеку
| Параметр | Что передавали |
|---|---|
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Текст последней ошибки, пустая строка при её отсутствии.
Ограничения и поведение
Сообщение относится к последней операции; прочитать до следующего MySQL-вызова. Оно может раскрыть имена схем, таблиц и детали сервера, поэтому старое die(mysql_error()) не переносим на публичную страницу.
$message = mysql_error($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
В исключении $e->getMessage(); детали — в защищённый журнал. В результате сценария публикуется классификация и код, а не SQL с учётными данными. Для ошибок соединения есть отдельная диагностика connect.
- Перехватить ошибку поля, сохранить сообщение и продолжить контролируемую работу.
- Отделить выбор схемы и подключение от обработки результата.
Подробный современный контракт: MySQLi: Сохранить текст диагностического отказа отдельно от ответа человеку. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_affected_rowsНе принять совпавшие строки за изменённые
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_affected_rows(resource $link_identifier=NULL): intНе принять совпавшие строки за изменённые
| Параметр | Что передавали |
|---|---|
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Число затронутых строк последней INSERT/UPDATE/REPLACE/DELETE; -1 после неудачного запроса.
Ограничения и поведение
Обычный UPDATE считал реально изменённые строки; прежнее значение даёт0. REPLACE суммировал удаление и вставку; UPSERT различал вставку1/обновление2. Считать до COMMIT; каскадные изменения FK не включаются. До MySQL4.1.2 DELETE без WHERE мог возвращать0.
$changed = mysql_affected_rows($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->affected_rows сразу после записи. Для SELECT используйте num_rows, а не этот счётчик. FOUND_ROWS меняет договор UPDATE; он отдельно разобран в главе редактирования.
- Показать insert2, прежнее значение0, изменение1 и delete1.
- true успешного UPDATE и число изменённых строк — разные данные.
Подробный современный контракт: MySQLi: Не принять совпавшие строки за изменённые. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_insert_idСохранить созданный AUTO_INCREMENT сразу после вставки
PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_insert_id(resource $link_identifier=NULL): int|falseСохранить созданный AUTO_INCREMENT сразу после вставки
| Параметр | Что передавали |
|---|---|
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Выданный AUTO_INCREMENT последней операции; 0, когда значение не выдавалось; false без подключения.
Ограничения и поведение
Прочитать немедленно после INSERT тем же link. Исторический PHP int мог не вместить BIGINT; SQL LAST_INSERT_ID() позволял получить значение без такого приведения и сохранял последнее выданное значение между запросами.
$id = mysql_insert_id($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->insert_id; современный контракт допускает int|string для больших значений. Не использовать SELECT MAX(id): он не принадлежит конкретной вставке. Для нескольких вставленных строк API возвращает первый автоматически выданный ID.
- После вставки двух товаров сразу сохранить первый ID1.
- ID товара — ключ, который нельзя путать со смещением строки результата.
Подробный современный контракт: MySQLi: Сохранить созданный AUTO_INCREMENT сразу после вставки. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
mysql_infoПрочитать пояснение записи, не считать его таблицей результата
PHP4.3.0–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi
mysql_info(resource $link_identifier=NULL): string|falseПрочитать пояснение записи, не считать его таблицей результата
| Параметр | Что передавали |
|---|---|
| link_identifier | Необязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно. |
Возврат и отказ старого API
Информационная строка для поддерживаемой команды; false, когда информации нет или операция не подходит.
Ограничения и поведение
Исторические формы: многорядная INSERT VALUES / INSERT SELECT / LOAD DATA — records/duplicates/warnings; ALTER — records/duplicates/warnings; UPDATE — matched/changed/warnings. Для одиночной INSERT VALUES информация не обещалась.
$information = mysql_info($link);
Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.
Современная замена и два практических случая
$db->info может вернуть строку или null. Сохраняем для диагностики сразу после команды, но решение приложения строим на affected_rows, ID и контрольном чтении, не на парсинге локализованного текста.
- Сохранить сведения двух UPDATE и проверить конечный остаток7.
- Отделить сообщение об ошибке от нормального результата записи.
Подробный современный контракт: MySQLi: Прочитать пояснение записи, не считать его таблицей результата. Современные соединения и результаты нельзя передавать старым mysql_* функциям.
Источники и обсуждения
- PHP: function.mysql-connect
- PHP: function.mysql-pconnect
- PHP: function.mysql-close
- PHP: function.mysql-select-db
- PHP: function.mysql-client-encoding
- PHP: function.mysql-set-charset
- PHP: function.mysql-escape-string
- PHP: function.mysql-real-escape-string
- PHP: function.mysql-query
- PHP: function.mysql-db-query
- PHP: function.mysql-unbuffered-query
- PHP: function.mysql-errno
- PHP: function.mysql-error
- PHP: function.mysql-affected-rows
- PHP: function.mysql-insert-id
- PHP: function.mysql-info
- PHP: function.mysql-fetch-array
- PHP: function.mysql-fetch-assoc
- PHP: function.mysql-fetch-row
- PHP: function.mysql-fetch-object
- PHP: function.mysql-result
- PHP: function.mysql-num-rows
- PHP: function.mysql-num-fields
- PHP: function.mysql-data-seek
- PHP: function.mysql-fetch-lengths
- PHP: function.mysql-free-result
- PHP: book.mysql
- PHP: changelog.mysql
- PHP: mysql.setup
- PHP: mysql.requirements
- PHP: mysql.installation
- PHP: mysql.configuration
- PHP: mysql.resources
- PHP: mysql.constants
- PHP: mysql.examples
- PHP: mysql.examples-basic
- PHP: ref.mysql
- PHP: mysqli-result.fetch-object
- PHP: mysqli-result.fetch-assoc
- PHP: mysqli-result.fetch-array
- PHP: mysqli-result.fetch-row
- PHP: mysqli-result.data-seek
- PHP: mysqli-result.num-rows
- PHP: mysqli-result.lengths
- PHP: mysqli.real-escape-string
- PHP: mysqli.persistconns
- PHP: mysqli.close
- PHP: mysqli.query
- PHP: mysqli.insert-id
- PHP: mysqli.info
- PHP: mysqli-result.free
- PHP: mysqli-driver.report-mode
- PHP: mysqli.character-set-name
- PHP: mysqli.set-charset
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


