Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

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

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 107 · После соединения и первого SELECT

Старый mysql_*: строки, объекты, курсор и освобождение результата

От формата одной строки до aliases, конструктора, замены mysql_result, байтовых длин, NULL и владения результатами.

Результат, строка, ячейка и курсор — разные вещи

Набор SELECT — таблица ответа со своим указателем текущей строки. Запрос может вернуть0 строк, но сохранить сведения о выбранных полях. Следующий fetch двигает указатель вперёд; data_seek меняет позицию только в уже полученном буфере. Здесь переносим чтение старого кода так, чтобы не потерять имена, NULL и порядок обработки.

Реальная строка каталога
  1. SELECT id,name WHERE id=1
  2. Ответ сервера:1 / Чай
  3. mysqli_result хранит ответ
  4. fetch_assoc →[id=>1,name=>Чай]
  5. Следующий fetch →null: строк больше нет
  6. HTML использует выбранные значения с htmlspecialchars

В старом ext/mysql конец fetch обозначался false. В MySQLi конец — null, а false может обозначать отказ. Нельзя оставить условие !==false без пересмотра. Подготовка среды и полный путь подключения находятся в первой главе блока.

Выбрать форму одной строки: имена, номера или оба набора ключей

Шаблон страницы ждёт $row[1], а перенесённый код выдаёт $row["name"]. Сравним договоры одного товара, чтобы выбрать подходящий формат и не дублировать данные случайно.

Путь данных: Выбрать форму одной строки: имена, номера или оба набора ключей
  1. Одинаковый SELECT товараID1
  2. ASSOC →id/name
  3. NUM →0/1
  4. BOTH →оба набора ключей
  5. Следующий fetch исчерпанного набора →null
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$row = $result->fetch_assoc();
echo $row['name'];
// Вместо этого можно fetch_row() и $row[1].

ASSOC сопоставляет имена id/name со значениями. NUM использует0/1 по порядку SELECT. BOTH содержит обе формы ключей одной строки. Указатель набора перемещается при каждом fetch, поэтому для сравнения выполняются три одинаковых запроса, а не три последовательных чтения разных товаров.

Что должно получиться

РежимКлючиIDИмя
ASSOCid, name1Чай
NUM0, 11Чай
BOTH0, id, 1, name1Чай
Следующий fetch после концаnullNULLNULL

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Выбрать форму одной строки: имена, номера или оба набора ключейСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
    $sql='SELECT id,name FROM mysql108_legacy_products WHERE id=1';
    $a=$db->query($sql);$assoc=$a->fetch_assoc();$end=$a->fetch_assoc();$a->free();
    $b=$db->query($sql);$numeric=$b->fetch_row();$b->free();
    $c=$db->query($sql);$both=$c->fetch_array(MYSQLI_BOTH);$c->free();
    $rows=[['ASSOC',implode(', ',array_keys($assoc)),(int)$assoc['id'],$assoc['name']],['NUM',implode(', ',array_keys($numeric)),(int)$numeric[0],$numeric[1]],['BOTH',implode(', ',array_keys($both)),(int)$both[0],$both['name']],['Следующий fetch после конца','null',null,null]];
    $checks=['assoc_names'=>array_keys($assoc)===['id','name'],'numeric_indices'=>array_keys($numeric)===[0,1],'both_contains_two_views'=>count($both)===4 && $both[1]===$both['name'],'end_is_null'=>$end===null];
    $details=['both_actual_keys'=>array_keys($both),'id_type_observed'=>get_debug_type($assoc['id'])];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-fetch-shapes', ['Режим', 'Ключи', 'ID', 'Имя'], $rows, $checks, $runtime, $details);
echo "ASSOC, NUM and BOTH read same product; EOF is null\n";
Ожидаемый результат
ASSOC, NUM and BOTH read same product; EOF is null

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Главная ловушка миграции: старое !==false не является верным условием конца современного fetch. NULL внутри поля отличается от null вместо всей строки. Тип ID может зависеть от протокола/настроек драйвера; наш прикладной отчёт явно приводит известный INT к int, наблюдаемый исходный тип записан отдельно.

Попробовать самостоятельно

Поменяйте SELECT на name,id. Решение: числовые индексы поменяются, именованные ключи сохранят смысл; не исправляйте это SELECT *.

Сохранить два одноимённых поля JOIN с помощью AS

Страница выводит название склада вместо названия товара. Оба поля JOIN назывались name, а ассоциативный массив способен хранить только одно значение для одинакового ключа.

Путь данных: Сохранить два одноимённых поля JOIN с помощью AS
  1. SQL возвращает p.name=Чай и s.name=Склад А
  2. fetch_assoc без AS →name=Склад А
  3. AS product_name / warehouse_name
  4. fetch_assoc →оба значения сохранены
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$sql = 'SELECT p.name AS product_name,s.name AS warehouse_name FROM ...';
$row = $result->fetch_assoc();
echo $row['product_name'], ' / ', $row['warehouse_name'];

AS назначает имя выходной колонки, не переименовывает колонку таблицы. product_name и warehouse_name образуют два понятных ключа результата. field_count показывает2 SQL-поля даже у ошибочной проекции; count($row) без AS оказывается1.

Что должно получиться

ВыборкаКлючей PHPПервое значениеВторое значение / пояснение
Без AS1Склад Авторое поле заняло имя name
С AS2ЧайСклад А

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Сохранить два одноимённых поля JOIN с помощью ASСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
    $ambiguous=$db->query('SELECT p.name,s.name FROM mysql108_legacy_products p JOIN (SELECT 1 AS product_id,\'Склад А\' AS name) s ON s.product_id=p.id');
    $lost=$ambiguous->fetch_assoc();$ambiguous->free();
    $correct=$db->query('SELECT p.name AS product_name,s.name AS warehouse_name FROM mysql108_legacy_products p JOIN (SELECT 1 AS product_id,\'Склад А\' AS name) s ON s.product_id=p.id');
    $kept=$correct->fetch_assoc();$fieldCount=$correct->field_count;$correct->free();
    $rows=[['Без AS',count($lost),$lost['name'],'второе поле заняло имя name'],['С AS',count($kept),$kept['product_name'],$kept['warehouse_name']]];
    $checks=['collision_demonstrated'=>count($lost)===1 && $lost['name']==='Склад А','both_preserved'=>$kept===['product_name'=>'Чай','warehouse_name'=>'Склад А'],'two_projected_fields'=>$fieldCount===2];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-column-aliases', ['Выборка', 'Ключей PHP', 'Первое значение', 'Второе значение / пояснение'], $rows, $checks, $runtime, $details);
echo "Aliases preserved product and warehouse names\n";
Ожидаемый результат
Aliases preserved product and warehouse names

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Вторая таблица склада в примере задана маленьким производным SELECT, чтобы сценарий был самостоятельным. В реальном JOIN схема и права остаются отдельной задачей. Не экранируйте пользовательское имя колонки как строку; для динамической сортировки нужна allowlist.

Попробовать самостоятельно

Добавьте p.id AS product_id. Решение: в правильном результате появится третий явный ключ, не изменяющий смысл первых двух.

Получить объект строки и увидеть порядок заполнения свойств

Старый код получал объект строки; при переносе конструктор неожиданно стирает имя товара. Покажем действительный порядок: сначала поля, потом конструктор.

Путь данных: Получить объект строки и увидеть порядок заполнения свойств
  1. SELECT id1/nameЧай
  2. Создание Legacy108Product
  3. Свойства id/name заполнены
  4. Конструктор получает префикс
  5. label →Товар: Чай
  6. Вторая строка без класса →stdClass
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$product = $result->fetch_object(Legacy108Product::class, ['Товар: ']);
echo $product->label; // Товар: Чай

Класс описывает свойства id/name и действие конструктора. fetch_object(Legacy108Product::class,["Товар: "]) создаёт объект, заполняет свойства и вызывает конструктор с нашим префиксом. Конструктор видит уже загруженное name и формирует label. Без класса возвращается stdClass.

Что должно получиться

ВариантIDИмяРезультат конструктора
Свой класс1ЧайТовар: Чай
stdClass2Кофебез пользовательского конструктора

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Получить объект строки и увидеть порядок заполнения свойствСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
class Legacy108Product {
    public $id; public $name; public string $label;
    public function __construct(string $prefix) {$this->label=$prefix.$this->name;}
}
$db=legacy108Connect();legacy108Catalog($db);
try {
    $result=$db->query('SELECT id,name FROM mysql108_legacy_products WHERE id=1');
    $product=$result->fetch_object(Legacy108Product::class,['Товар: ']);$lengths=$result->lengths;$result->free();
    $plainResult=$db->query('SELECT id,name FROM mysql108_legacy_products WHERE id=2');$plain=$plainResult->fetch_object();$plainResult->free();
    $rows=[['Свой класс',(int)$product->id,$product->name,$product->label],['stdClass',(int)$plain->id,$plain->name,'без пользовательского конструктора']];
    $checks=['custom_class'=>$product instanceof Legacy108Product,'properties_before_constructor'=>$product->label==='Товар: Чай','default_stdclass'=>$plain instanceof stdClass && $plain->name==='Кофе','lengths_after_object'=>$lengths[1]===6];$details=['name_bytes_after_object'=>$lengths[1]];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-object-hydration', ['Вариант', 'ID', 'Имя', 'Результат конструктора'], $rows, $checks, $runtime, $details);
echo "Custom constructor saw fetched name; default row is stdClass\n";
Ожидаемый результат
Custom constructor saw fetched name; default row is stdClass

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Аргументы конструктора — переданный программистом список, не массив полей SQL. Если нужны проверки инвариантов до присваивания данных, выберите fetch_assoc и явное создание доменного объекта. Объявленные свойства избегают случайных динамических свойств. Пользователь не должен выбирать имя класса из запроса.

Попробовать самостоятельно

Добавьте в конструктор $this->name="Заменено". Решение: загруженное имя будет затёрто. Это демонстрация порядка, а не рекомендация так проектировать объект.

Практическая ситуация: How to use mysqli::fetch_object()?. Автор хотел автоматически передать SQL-строку массивом в конструктор нескольких классов. Пример объясняет собственные аргументы конструктора и порядок заполнения свойств; явное fetch_assoc остаётся альтернативой. Вопрос используется как описание проблемы; код и объяснение написаны заново, технические правила сверены с PHP.net.

Заменить mysql_result: строка буфера и точечный запрос по ID

После замены mysql_ на mysqli_ функция mysqli_result не нашлась. Нужно определить настоящую задачу: третья строка уже готового набора или товар с известным ID.

Путь данных: Заменить mysql_result: строка буфера и точечный запрос по ID
  1. БуферORDER BY id:1,2,3,4
  2. data_seek2 →ID3 Какао
  3. data_seek0 →ID1 Чай
  4. WHERE id3 →Какао
  5. WHERE id99 →нет строки/null
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$result->data_seek(2); // третья строка существующего буфера
$row = $result->fetch_assoc();
$name = $row['name'];

data_seek(2) переставляет указатель буфера на третью строку, потому что счёт начинается с0. Последующий fetch_assoc извлекает её. WHERE id=? ищет конкретный ключ на сервере и не зависит от позиции других строк. SQL ORDER BY делает учебный порядок воспроизводимым.

Что должно получиться

ДоступIDЗначение
Смещение 2 в ORDER BY id3Какао
Смещение 01Чай
WHERE id=33Какао
WHERE id=9999NULL

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Заменить mysql_result: строка буфера и точечный запрос по IDСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
    $buffer=$db->query('SELECT id,name FROM mysql108_legacy_products ORDER BY id');
    $moved=$buffer->data_seek(2);$third=$buffer->fetch_assoc();$buffer->data_seek(0);$first=$buffer->fetch_assoc();$count=$buffer->num_rows;$buffer->free();
    $id=3;$stmt=$db->prepare('SELECT name FROM mysql108_legacy_products WHERE id=?');$stmt->bind_param('i',$id);$stmt->execute();$one=$stmt->get_result();$target=$one->fetch_assoc();$one->free();
    $id=99;$stmt->execute();$none=$stmt->get_result();$missing=$none->fetch_assoc();$none->free();$stmt->close();
    $rows=[['Смещение 2 в ORDER BY id',(int)$third['id'],$third['name']],['Смещение 0',(int)$first['id'],$first['name']],['WHERE id=3',3,$target['name']],['WHERE id=99',99,$missing]];
    $checks=['seek_third'=>$moved && (int)$third['id']===3,'seek_back'=>(int)$first['id']===1,'buffer_count'=>$count===4,'specific_id'=>$target['name']==='Какао','missing_is_null'=>$missing===null];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-seek-cell', ['Доступ', 'ID', 'Значение'], $rows, $checks, $runtime, $details);
echo "Buffer position2 and ID3 found Cocoa; missing ID99 stayed null\n";
Ожидаемый результат
Buffer position2 and ID3 found Cocoa; missing ID99 stayed null

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Позиция2 не равна ID2; без ORDER BY нет устойчивого порядка. Пустой набор нельзя seek(0). Не сканируйте все товары ради одного ключа. Для поля, содержащего SQL NULL, array_key_exists и сравнение строки целиком помогают не спутать отсутствие значения и отсутствие записи.

Попробовать самостоятельно

Поменяйте ORDER BY id DESC. Решение: позиция2 теперь ID2, но WHERE id=3 по-прежнему возвращает Какао.

Практическая ситуация: MySQLi equivalent of mysql_result()?. При переносе чтения одной ячейки не нашлось метода result. Мы разделяем доступ к готовому буферу по позиции и запрос нужного поля по идентификатору, не создаём фиктивную встроенную функцию. Вопрос используется как описание проблемы; код и объяснение написаны заново, технические правила сверены с PHP.net.

Не перепутать байты текста, пустую строку и SQL NULL

Экспортёр видит нулевую длину и решает, что значения нет. Но пустой комментарий и SQL NULL имеют разный смысл; слово Чай занимает больше байтов, чем букв.

Путь данных: Не перепутать байты текста, пустую строку и SQL NULL
  1. Прочитать title/empty_text/missing_text/zero_text
  2. lengths →6,0,0,1
  3. Проверить значения отдельно
  4. NULL остаётся отсутствием, "" — пустым текстом, "0" — текстовым нулём
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$row = $result->fetch_assoc();
$byteLengths = $result->lengths;
$isMissing = $row['missing_text'] === null;

fetch_assoc получает текущую строку, затем lengths возвращает размеры её колонок. title имеет6 UTF-8 байтов; empty_text и missing_text имеют длину0, но первое значение "", второе null. zero_text содержит один байт0. field_count сообщает число колонок4.

Что должно получиться

ПолеЗначениеБайт в результатеСмысл
titleЧай6строка
empty_text(пустая строка)0строка
missing_textNULL0SQL NULL
zero_text01строка

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Не перепутать байты текста, пустую строку и SQL NULLСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();
try {
    $r=$db->query("SELECT 'Чай' AS title,'' AS empty_text,NULL AS missing_text,'0' AS zero_text");
    $record=$r->fetch_assoc();$lengths=$r->lengths;$fields=$r->field_count;$r->free();
    $rows=[];$names=array_keys($record);
    foreach ($names as $i=>$name) {$rows[]=[$name,$record[$name],$lengths[$i],$record[$name]===null?'SQL NULL':'строка'];}
    $checks=['bytes_utf8'=>$lengths===[6,0,0,1],'four_fields'=>$fields===4,'null_distinct'=>$record['missing_text']===null && $record['empty_text']==='' && $record['zero_text']==='0','name_matches'=>$record['title']==='Чай'];
    $details=['php_strlen_title'=>strlen($record['title']),'character_length'=>3,'lengths_apply_to_last_fetched_row'=>true];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-lengths-null', ['Поле', 'Значение', 'Байт в результате', 'Смысл'], $rows, $checks, $runtime, $details);
echo "UTF-8 bytes6; empty0; NULL0; zero1; all four meanings retained\n";
Ожидаемый результат
UTF-8 bytes6; empty0; NULL0; zero1; all four meanings retained

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

lengths действует для последней прочитанной строки; до fetch и после конца у API другой результат. Это длина данных, не максимальный размер столбца. Для количества пользовательских символов нужны функции многобайтового текста; здесь число букв3 известно для фиксированного слова, не вычислено bytes/2.

Попробовать самостоятельно

Замените Чай на Tea. Решение: длина станет3байта при том же количестве букв; остальные значения не изменятся.

Разделить набор результата и соединение, которое его создало

Старый обработчик освобождал ресурс, после чего случайно терял возможность выполнить следующий запрос. Разделим два уровня владения: результат и подключение.

Путь данных: Разделить набор результата и соединение, которое его создало
  1. Первый SELECT →Кофе без остатка
  2. free первого результата
  3. Тем же db получить COUNT(*) →4
  4. Пустой SELECT →0 строк,2поля
  5. Закрыть db после всех операций
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$result->free(); // освобождён набор, не соединение
$next = $db->query('SELECT COUNT(*) AS total FROM products');
$db->close(); // закрывается соединение

mysqli_result содержит ответ конкретного SELECT. free завершает его использование. $db всё ещё представляет соединение и может выполнить другой query. Даже пустой набор содержит схему выбранных колонок:0 строк и2 поля не противоречат друг другу.

Что должно получиться

ДействиеСтрокПояснение
Первый набор до free1Кофе
Новый запрос тем же соединением4соединение осталось открытым
Пустой набор0полей: 2

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Разделить набор результата и соединение, которое его создалоСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
    $first=$db->query('SELECT id,name FROM mysql108_legacy_products WHERE stock=0');$before=$first->num_rows;$record=$first->fetch_assoc();$first->free();
    $second=$db->query('SELECT COUNT(*) AS total FROM mysql108_legacy_products');$total=(int)$second->fetch_assoc()['total'];$second->free();
    $empty=$db->query('SELECT id,name FROM mysql108_legacy_products WHERE id<0');$emptyRows=$empty->num_rows;$emptyFields=$empty->field_count;$empty->free();
    $rows=[['Первый набор до free',$before,$record['name']],['Новый запрос тем же соединением',$total,'соединение осталось открытым'],['Пустой набор',$emptyRows,'полей: '.$emptyFields]];
    $checks=['out_of_stock_found'=>$before===1 && $record['name']==='Кофе','connection_not_result'=>$total===4,'empty_has_schema'=>$emptyRows===0 && $emptyFields===2];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-result-lifetime', ['Действие', 'Строк', 'Пояснение'], $rows, $checks, $runtime, $details);
echo "Freed results independently; connection reused; empty result has2fields\n";
Ожидаемый результат
Freed results independently; connection reused; empty result has2fields

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Не вызывать free на true от UPDATE, не читать уже освобождённый объект. Подготовленный statement имеет собственный close; он не заменяет free результата. Современный free возвращает void, поэтому if(free()) не переносится из исторического bool-контракта.

Попробовать самостоятельно

Добавьте третье явное поле stock к пустой выборке. Решение: строк по-прежнему0, полей3; схема ответа существует независимо от данных.

Как проверить перенос в своём проекте

Переносите одну наблюдаемую операцию целиком: входные параметры, SQL, тип результата, контроль ошибок, данные на экране. После этого переходите к фильтрам и подготовленным запросам, конфликтам редактирования и транзакциям. Простая замена префикса не проверяет ни один из этих договоров.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

mysql_fetch_arrayВыбрать ассоциативные и числовые ключи строки

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_fetch_array(resource $result, int $result_type=MYSQL_BOTH): array|false

Выбрать ассоциативные и числовые ключи строки

ПараметрЧто передавали
resultРесурс результата SELECT, не link.
result_typeMYSQL_ASSOC, MYSQL_NUM или MYSQL_BOTH; по умолчанию оба вида ключей.

Возврат и отказ старого API

Массив очередной строки; старый конец набора — false. Обычные значения представлены строками, SQL NULL — PHP null; курсор сдвигается.

Ограничения и поведение

Имена ключей чувствительны к регистру. Повторное имя столбца перекрывается последним; числовой индекс либо AS сохраняет различимость. BOTH даёт два способа доступа, а не вдвое больше строк.

Историческая запись mysql_fetch_array — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$row = mysql_fetch_array($result, MYSQL_ASSOC);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

mysqli_fetch_array($result,MYSQLI_BOTH); конец теперь null, ошибка false. Не заменяйте старый цикл !==false механически. Выбор формы — договор с кодом, а не обещание заметного выигрыша скорости.

Подробный современный контракт: MySQLi: Выбрать ассоциативные и числовые ключи строки. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_fetch_assocПрочитать строку с понятными именами полей

PHP4.0.3–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_fetch_assoc(resource $result): array|false

Прочитать строку с понятными именами полей

ПараметрЧто передавали
resultРесурс набора, полученный запросом.

Возврат и отказ старого API

Ассоциативная строка и продвижение курсора; false в конце. Значения-строки, SQL NULL преобразуется в PHP null.

Ограничения и поведение

Эквивалент fetch_array с MYSQL_ASSOC. Имена чувствительны к регистру; повторяющиеся имена перекрываются последним столбцом. Не полагайтесь на скорость как причину выбирать неясные числовые индексы.

Историческая запись mysql_fetch_assoc — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$row = mysql_fetch_assoc($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$result->fetch_assoc() возвращает array|null|false. Явные SELECT-поля и AS задают понятный контракт приложения. Отсутствующая строка отличается от существующей строки с NULL в поле.

Подробный современный контракт: MySQLi: Прочитать строку с понятными именами полей. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_fetch_rowПрочитать строку по порядку SELECT-полей

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_fetch_row(resource $result): array|false

Прочитать строку по порядку SELECT-полей

ПараметрЧто передавали
resultРесурс результата; функция берёт следующую строку.

Возврат и отказ старого API

Массив с индексами0,1,…; false в конце старого набора. Значения-строки, NULL остаётся null.

Ограничения и поведение

Порядок определяется списком SELECT, не расположением колонок в таблице. Добавленный столбец может сдвинуть индексы; не связывайте интерфейс с SELECT *.

Историческая запись mysql_fetch_row — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$row = mysql_fetch_row($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$result->fetch_row() возвращает array|null|false. Уместно для заданного позиционного формата; для формы редактирования обычно понятнее имена.

Подробный современный контракт: MySQLi: Прочитать строку по порядку SELECT-полей. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_fetch_objectПонять превращение строки в свойства объекта

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_fetch_object(resource $result, string $class_name="stdClass", array $params=[]): object|false

Понять превращение строки в свойства объекта

ПараметрЧто передавали
resultСледующая строка результата.
class_nameКласс создаваемого объекта; по умолчанию stdClass.
paramsАргументы конструктора пользовательского класса, не автоматический массив всех SQL-полей.

Возврат и отказ старого API

Объект со свойствами по именам столбцов; false в конце. Старые значения-строки, SQL NULL — null.

Ограничения и поведение

Доступ по именам свойств, не по номерам колонок; имена чувствительны к регистру. Не путайте заполнение свойств с вызовами setter. При переносе проверьте инварианты и конструктор.

Историческая запись mysql_fetch_object — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$product = mysql_fetch_object($result, "Product", array("Товар: "));

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

Современный fetch_object заполняет свойства ДО конструктора; конструктор может их изменить. Возврат object|null|false. Если классу нужен массив данных — сначала fetch_assoc, затем свой конструктор явно.

Подробный современный контракт: MySQLi: Понять превращение строки в свойства объекта. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_resultЗаменить обращение к одной ячейке без потери смысла

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_result(resource $result, int $row, mixed $field=0): string|false

Заменить обращение к одной ячейке без потери смысла

ПараметрЧто передавали
resultРесурс набора.
rowПозиция строки начиная с0; не первичный ключ.
fieldИндекс поля, имя или table.field; при AS используется псевдоним; по умолчанию первое поле.

Возврат и отказ старого API

Содержимое ячейки либо false при ошибке. Это историческая сигнатура; SQL NULL не следует превращать в пустую строку при миграции.

Ограничения и поведение

Не рекомендовалось смешивать вызовы с другими операциями курсора. Чтение каждой ячейки отдельно добавляет вызовы; обычно понятнее сразу получить строку. Объявлять универсальный mysqli_result() как официальную функцию неверно.

Историческая запись mysql_result — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$name = mysql_result($result, 2, "name");

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

Для одной нужной записи: SELECT нужных полей WHERE id=?, затем fetch_assoc. Для уже имеющегося буфера: data_seek($offset) и fetch одной строки с проверкой границ.

Подробный современный контракт: MySQLi: Заменить обращение к одной ячейке без потери смысла. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_num_rowsПосчитать строки результата, а не строки таблицы вообще

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_num_rows(resource $result): int|false

Посчитать строки результата, а не строки таблицы вообще

ПараметрЧто передавали
resultРесурс SELECT/SHOW с набором; не true от UPDATE.

Возврат и отказ старого API

Количество строк результата либо false при ошибке.

Ограничения и поведение

При unbuffered корректного общего числа до полного чтения нет. Число строк SELECT COUNT(*) обычно1, а искомый счётчик находится внутри этой строки. Исторический alias mysql_numrows устарел.

Историческая запись mysql_num_rows — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$count = mysql_num_rows($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$result->num_rows для буферизованного набора; для потока — собственный счётчик после чтения. Ноль не ошибка. Для изменения данных — affected_rows.

Подробный современный контракт: MySQLi: Посчитать строки результата, а не строки таблицы вообще. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_num_fieldsПосчитать выбранные колонки даже у пустого набора

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_num_fields(resource $result): int|false

Посчитать выбранные колонки даже у пустого набора

ПараметрЧто передавали
resultРесурс результата запроса.

Возврат и отказ старого API

Количество полей результата, false при отказе.

Ограничения и поведение

Это ширина SELECT-проекции; не число всех полей таблицы и не длина текста. Результат с0 строк может иметь несколько полей. Исторический alias mysql_numfields устарел.

Историческая запись mysql_num_fields — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$fields = mysql_num_fields($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$result->field_count; подробные имена/типы доступны через fetch_fields. Не получать все поля только ради подсчёта нужных приложению.

Подробный современный контракт: MySQLi: Посчитать выбранные колонки даже у пустого набора. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_data_seekПередвинуть курсор уже полученного буфера

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_data_seek(resource $result, int $row_number): bool

Передвинуть курсор уже полученного буфера

ПараметрЧто передавали
resultБуферизованный ресурс mysql_query.
row_numberПозиция от0 до num_rows-1; не ID записи.

Возврат и отказ старого API

true при переходе, false при отказе. Пустой набор и seek(0) — E_WARNING/false в старом API.

Ограничения и поведение

Следующий fetch читает указанную позицию. Не работает с mysql_unbuffered_query. Без ORDER BY порядок строк SQL не является устойчивой нумерацией.

Историческая запись mysql_data_seek — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
mysql_data_seek($result, 2);
$row = mysql_fetch_assoc($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$result->data_seek($offset) только для буфера. Проверяйте допустимую позицию; запрос к серверу не выполняется повторно. Для известного ID используйте WHERE, для страницы — ORDER BY/LIMIT.

Подробный современный контракт: MySQLi: Передвинуть курсор уже полученного буфера. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_fetch_lengthsИзмерить значения последней прочитанной строки

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_fetch_lengths(resource $result): array|false

Измерить значения последней прочитанной строки

ПараметрЧто передавали
resultРесурс, у которого уже вызвали fetch_row/assoc/array/object.

Возврат и отказ старого API

Числовой массив длин полей последней строки, индекс начиная с0; false при отказе.

Ограничения и поведение

Длины относятся к фактическому результату, не к объявленному VARCHAR(N). Для UTF-8 длина в байтах не равна числу букв; нулевая длина не отличает NULL от пустой строки без проверки самого значения.

Историческая запись mysql_fetch_lengths — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$row = mysql_fetch_assoc($result);
$lengths = mysql_fetch_lengths($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

После fetch используйте $result->lengths или mysqli_fetch_lengths($result). У свойства современный nullable-контракт, у процедуры возможен false; не переносите одну проверку на оба варианта.

Подробный современный контракт: MySQLi: Измерить значения последней прочитанной строки. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_free_resultОсвободить память результата, сохранив соединение

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_free_result(resource $result): bool

Освободить память результата, сохранив соединение

ПараметрЧто передавали
resultРесурс результата SELECT/SHOW/EXPLAIN/DESCRIBE; не bool от изменения.

Возврат и отказ старого API

true при освобождении, false при отказе; нересурс вызывал E_WARNING.

Ограничения и поведение

Память освобождалась и при завершении скрипта. Явное освобождение полезно после обработки больших наборов. Исторический alias mysql_freeresult устарел; дальнейшее чтение освобождённого ресурса недопустимо.

Историческая запись mysql_free_result — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
mysql_free_result($result);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$result->free() освобождает mysqli_result; его современный возврат void не равен старому bool. Не закрывайте $db между связанными командами только ради освобождения одного набора.

Подробный современный контракт: MySQLi: Освободить память результата, сохранив соединение. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.