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

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

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

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

Глава 127 · После таблиц и чтения SQL через X Protocol

MySQL X DevAPI: столбцы результата, типы и понятный отчёт

От имени одной колонки до готового HTML-каталога: aliases и исходные поля, типы DECIMAL и BIGINT, Unicode, пустая выдача и безопасный вывод реальных строк.

От ответа базы к понятному отчёту

Каталог уже умеет читать товары из MySQL. Следующая потребность — показать их понятно: в какой колонке название, какая цена пришла точной строкой, откуда взялось поле после JOIN и какие заголовки оставить, если фильтр ничего не нашёл. Здесь мы получим эти сведения от результата запроса, а затем соберём настоящий HTML-отчёт с двумя товарами.

У ответа есть описание и значения
  1. SELECT name AS product_name выбирает поле и имя ответа
  2. Result хранит описание проекции и строки
  3. getColumns → ColumnResult: название, происхождение, тип
  4. fetchOne / fetchAll → значения товаров
  5. словарь интерфейса + значения → таблица для человека

Представьте ведомость: заголовок «Цена, руб.» объясняет колонку, а 12,30 — значение в строке конкретного товара. Metadata — техническое описание колонки; данные — сами значения. Это сравнение помогает разделить два слоя, но сервер не знает всех правил интерфейса: валюту, русский заголовок и смысл пустого примечания задаёт приложение.

Первый понятный вызов: имя одного столбца

Пусть в подготовленной таблице есть name со значением «Светильник». AS product_name даёт этому полю имя только в ответе запроса: таблица на сервере не переименовывается. getColumns возвращает массив описаний; [0] выбирает первое описание. Вызов метода через стрелку читает его свойство, а fetchOne отдельно читает строку.

$result = $table->select('name AS product_name')->limit(1)->execute();
$columns = $result->getColumns();
$label = $columns[0]->getColumnLabel();
$row = $result->fetchOne();
echo $label, ': ', $row[$label]; // product_name: Светильник

Здесь $table — уже полученный объект учебной таблицы, и строка заведомо существует. Полные программы ниже создают все свои исходные данные; отдельный пример показывает нулевой ответ. До этой главы нужны выбор полей таблицы, передача SQL-параметров и чтение строки. Короткий вызов не заменяет подключение: оно находится в скачиваемом файле вместе с подготовкой базы.

Что именно узнаём, а что задаём сами

СлойПримерДля чего
SQL-источникproducts.nameУзнать происхождение прямого поля
Метка результатаproduct_nameОбратиться к ключу ассоциативного массива
Человеческий заголовокТоварОбъяснить содержание читателю
Описание типаDECIMAL, scale 2Проверить контракт точной цены
Реальное значениестрока «12.30»Показать «12,30» без промежуточного float

Описание не заменяет SQL-схему целиком и не является указанием загружать все данные. SELECT перечисляет только нужные поля; WHERE, ORDER BY и LIMIT ограничивают строки на сервере. Здесь мы не выгружаем большой каталог, чтобы сортировать его в PHP. Сборка таблицы проходит по уже выбранным строкам экрана. Наличие metadata также не доказывает наличие хотя бы одного товара.

Как запустить и открыть результат

Нужны отдельная учебная схема и X Protocol: подготовка лаборатории. Профиль этой главы — PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13. Параметры подключения берутся из PHPMAN_MYSQLX_URI и PHPMAN_MYSQLX_SCHEMA; пароль не хранится в архиве. README каждого архива описывает запуск и необходимые права. Сценарии создают только собственные таблицы mysql142_… и удаляют их после чтения результата.

Сначала выполните CLI-файл из архива. result.html покажет фактические наблюдения, result.json сохранит значения, их типы и metadata, metrics.json зафиксирует проверки и очистку. Последняя программа дополнительно создаёт report.html — сам каталог, который можно открыть в браузере. Это разные файлы: техническая таблица помогает понять действие API, а каталог показывает его полезный результат. Проверка подключения и выполнение SQL происходят в PHP, не имитируются браузером.

В выбранной версии объект ColumnResult ссылается на внутреннее описание исходного Result. Держите $result живым до завершения всех getter-вызовов. Самостоятельный metadata.php превращает описания в обычный массив строк, чисел, bool и null, который затем безопасно сохраняется в JSON. Сохранение такого массива не означает сохранение работающего соединения.

Есть расхождения с краткими страницами Manual: фактический класс — ColumnResult; пустой TableSelect сохраняет описания полей, а пустой SQL SELECT в этой связке их не предоставляет. Для такого пустого экрана заголовки берутся из собственного контракта проекции. isNumberSigned/isPadded дают bool, а имена кодировки числовых полей могут быть null. Официальные сигнатуры сохранены в справочнике ниже, фактические различия отмечены явно. Эти сведения относятся к названному профилю и не объявляются контрактом любого MySQL-коннектора.

Связать имя в таблице, псевдоним и значение товара

Для карточки товара база хранит name, а интерфейс ожидает product_name. Нужно увидеть, какое имя поменялось и по какому ключу искать значение. Получим не просто список свойств объекта, а три строки соответствия вместе с реальными id, названием и ценой.

Путь данных этой программы
  1. В таблице: id=1, name=Светильник, price=12.34
  2. SELECT назначает product_id, product_name, unit_price
  3. getColumns возвращает 3 описания, не читая товар
  4. getColumnName даёт name; getColumnLabel даёт product_name
  5. fetchOne даёт строку; по label читаем Светильник

Сначала короткое действие

$result = $table->select('name AS product_name')->execute();
$columns = $result->getColumns();
$row = $result->fetchOne();
echo $columns[0]->getColumnName();  // name
echo $columns[0]->getColumnLabel(); // product_name
echo $row['product_name'];         // Светильник

AS назначает имя только в результате этого SELECT. Исходная колонка на сервере не переименовывается. $columns — нумерованный массив объектов описания, поэтому первый элемент имеет индекс 0. $row — ассоциативный массив данных одной строки: ключом является label. $result сохраняется в переменной всё время чтения metadata. В полной программе сначала создаётся собственная таблица и одна строка; SQL с фиксированными исходными значениями служит только подготовкой лаборатории.

Что получится

НаблюдениеРезультат
id → product_id1
name → product_nameСветильник
price → unit_price12.34
Класс описания первого столбцаmysql_xdevapi\ColumnResult
Ключи строкиproduct_id, product_name, unit_price
Самостоятельный файл и архив для запуска
Связать имя в таблице, псевдоним и значение товараСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/metadata.php';

$config = x142Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x142OwnTable($session,$schema,$owned,'labels',
        '(id INT PRIMARY KEY, name VARCHAR(40) NOT NULL, price DECIMAL(10,2) NOT NULL) ENGINE=InnoDB');
    $qualified = x142Qualified($schema,$name);
    $table = $schema->getTable($name);
    $session->sql("INSERT INTO ".$qualified." (id,name,price) VALUES (1,'Светильник',12.34)")->execute();
    $result = $table->select('id AS product_id','name AS product_name','price AS unit_price')->execute();
    $columns = $result->getColumns();
    $meta = metadata142Columns($columns);
    $row = $result->fetchOne();
    $rows = [];
    foreach ($meta as $column) {
        $label = $column['label'];
        $rows[] = [$column['name'].' → '.$label,$row[$label]];
    }
    $rows[] = ['Класс описания первого столбца',$meta[0]['class']];
    $rows[] = ['Ключи строки',implode(', ',array_keys($row))];
    $checks = ['metadata_classes'=>array_column($meta,'class') === array_fill(0,3,'mysql_xdevapi\\ColumnResult'),
        'original_names'=>array_column($meta,'name') === ['id','name','price'],
        'labels'=>array_column($meta,'label') === ['product_id','product_name','unit_price'],
        'row_keys'=>array_keys($row) === array_column($meta,'label'),
        'row_values'=>$row === ['product_id'=>1,'product_name'=>'Светильник','unit_price'=>'12.34'],
        'metadata_no_read'=>$result->fetchOne() === null];
    $observations = ['metadata'=>$meta,'row'=>$row,'value_types'=>array_map('get_debug_type',$row)];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x142Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x142Report('mysql142-x-metadata-column-labels', 'Связать имя в таблице, псевдоним и значение товара', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
id → product_id — 1
name → product_name — Светильник
price → unit_price — 12.34
Класс описания первого столбца — mysql_xdevapi\ColumnResult
Ключи строки — product_id, product_name, unit_price

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт и удаляет только собственные объекты; metadata.php сохраняет описание колонок, пока жив исходный Result. Откройте result.html и result.json; в последней программе также report.html.

Наблюдения сохранённого запуска · Значения, типы и metadata.

Границы и ошибки

В PECL 8.0.30 фактически получен ColumnResult. На странице RowResult::getColumns Manual до сих пор указан FieldMetadata: старое описание нельзя принимать за класс текущего объекта. Не путайте label с русским текстом интерфейса: надпись «Товар» можно задать отдельным словарём. Для пустого ответа fetchOne даёт null; обращение к $row[label] допускается только после проверки наличия строки. Такой случай разобран далее.

Попробуйте изменить

Измените только AS product_name на AS caption. Что останется прежним, что нужно поменять при чтении строки?

Ожидаемое решение

Исходное имя name и сохранённое значение Светильник останутся прежними. Метка, ключ результата и обращение к $row должны стать caption; иначе код обратится к отсутствующему ключу.

Показать происхождение полей после JOIN

В отчёте нужны название товара и название категории. Обе таблицы содержат колонку name; без ясных aliases читателю и коду будет трудно различить их. Покажем товар вместе с категорией и ценой двух штук, а затем объясним, откуда пришло каждое поле.

Путь данных этой программы
  1. Категория 7 → Освещение
  2. Товар 1 хранит category_id=7 и name=Светильник
  3. JOIN связывает p.category_id с c.id
  4. p.name → product_name, c.name → category_name
  5. p.price * 2 → pair_price: 24.68, без одного исходного столбца

Сначала короткое действие

$sql = 'SELECT p.name AS product_name,c.name AS category_name '
    .'FROM '.$productQualified.' AS p '
    .'JOIN '.$categoryQualified.' AS c ON c.id=p.category_id WHERE p.id=?';
$result = $session->sql($sql)->bind(1)->execute();
$columns = $result->getColumns();
echo $columns[0]->getTableLabel(); // p
echo $columns[0]->getColumnName(); // name

В JOIN буквы p и c — короткие роли таблиц в этом запросе. ON определяет соответствие строк, а AS у колонки задаёт имя ключа ответа. getTableLabel читает роль p; getTableName возвращает настоящее созданное имя таблицы. Вместе со схемой и исходным именем поля это объясняет прямое происхождение. В полном архиве внешний ключ дополнительно проверяет, что category_id ссылается на существующую категорию.

Что получится

НаблюдениеРезультат
ТоварСветильник
КатегорияОсвещение
Цена пары24.68
product_name: роль и полеp.name
category_name: роль и полеc.name
Исходная таблица товара определенада
Исходная таблица категории определенада
Обе прямые колонки из учебной схемыда
pair_price: источникВычисление, единого исходного поля нет
Самостоятельный файл и архив для запуска
Показать происхождение полей после JOINСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/metadata.php';

$config = x142Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $categoryName = x142OwnTable($session,$schema,$owned,'categories',
        '(id INT PRIMARY KEY, name VARCHAR(40) NOT NULL) ENGINE=InnoDB');
    $categoryQualified = x142Qualified($schema,$categoryName);
    $productName = x142OwnTable($session,$schema,$owned,'products',
        '(id INT PRIMARY KEY, category_id INT NOT NULL, name VARCHAR(40) NOT NULL, price DECIMAL(10,2) NOT NULL, '
        .'FOREIGN KEY (category_id) REFERENCES '.$categoryQualified.' (id)) ENGINE=InnoDB');
    $productQualified = x142Qualified($schema,$productName);
    $session->sql("INSERT INTO ".$categoryQualified." (id,name) VALUES (7,'Освещение')")->execute();
    $session->sql("INSERT INTO ".$productQualified." (id,category_id,name,price) VALUES (1,7,'Светильник',12.34)")->execute();
    $sql = 'SELECT p.name AS product_name,c.name AS category_name,p.price * 2 AS pair_price '
        .'FROM '.$productQualified.' AS p JOIN '.$categoryQualified.' AS c ON c.id=p.category_id WHERE p.id=?';
    $result = $session->sql($sql)->bind(1)->execute();
    $columns = $result->getColumns();
    $meta = metadata142Columns($columns);
    $row = $result->fetchOne();
    $rows = [['Товар',$row['product_name']],['Категория',$row['category_name']],['Цена пары',$row['pair_price']],
        ['product_name: роль и поле',$meta[0]['table_label'].'.'.$meta[0]['name']],
        ['category_name: роль и поле',$meta[1]['table_label'].'.'.$meta[1]['name']],
        ['Исходная таблица товара определена',$meta[0]['table_name'] === $productName],
        ['Исходная таблица категории определена',$meta[1]['table_name'] === $categoryName],
        ['Обе прямые колонки из учебной схемы',$meta[0]['schema'] === $config['schema'] && $meta[1]['schema'] === $config['schema']],
        ['pair_price: источник',$meta[2]['table_name'] === '' && $meta[2]['name'] === '' ? 'Вычисление, единого исходного поля нет' : 'Неожиданные метаданные']];
    $checks = ['joined_row'=>$row === ['product_name'=>'Светильник','category_name'=>'Освещение','pair_price'=>'24.68'],
        'unique_labels'=>array_column($meta,'label') === ['product_name','category_name','pair_price'],
        'table_aliases'=>array_column($meta,'table_label') === ['p','c',''],
        'column_origins'=>array_column($meta,'name') === ['name','name',''],
        'physical_tables'=>$meta[0]['table_name'] === $productName && $meta[1]['table_name'] === $categoryName,
        'physical_schema'=>$meta[0]['schema'] === $config['schema'] && $meta[1]['schema'] === $config['schema'],
        'expression_origin_empty'=>$meta[2]['schema'] === '' && $meta[2]['table_name'] === ''];
    $observations = ['metadata'=>$meta,'row'=>$row,'owned_tables'=>['p'=>$productName,'c'=>$categoryName]];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x142Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x142Report('mysql142-x-metadata-column-origins', 'Показать происхождение полей после JOIN', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Товар — Светильник
Категория — Освещение
Цена пары — 24.68
product_name: роль и поле — p.name
category_name: роль и поле — c.name
Исходная таблица товара определена — да
Исходная таблица категории определена — да
Обе прямые колонки из учебной схемы — да
pair_price: источник — Вычисление, единого исходного поля нет

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт и удаляет только собственные объекты; metadata.php сохраняет описание колонок, пока жив исходный Result. Откройте result.html и result.json; в последней программе также report.html.

Наблюдения сохранённого запуска · Значения, типы и metadata.

Границы и ошибки

Происхождение вычисления не восстанавливается по последнему столбцу: price * 2 имеет собственный label, но пустые schema/table/name в metadata. Это нормальная граница результата, не повреждение базы. Для сложных вычислений зависимости задаются самим запросом. Metadata не даёт безопасного универсального способа превратить показанную ячейку в UPDATE: нужны ключ строки, права и предметные правила. Обе таблицы собственной лаборатории удаляются в обратном порядке, сначала дочерняя.

После SQL-псевдонимов разработчик видит имена результата, но хочет узнать исходные таблицу и колонку. Реальная задача на Stack Overflow. В X DevAPI отдельно читаем label и name из ColumnResult и не пытаемся восстановить SQL регулярным выражением. Вопрос относится к PDO; его ответы не задают контракт этого расширения.

Попробуйте изменить

Добавьте ещё один товар той же категории и замените WHERE по id на ORDER BY p.id. Какие имена metadata изменятся?

Ожидаемое решение

Количество строк станет два, а состав metadata прежний: три столбца, те же исходные имена и aliases. Метаданные описывают форму всех строк, а не отдельный товар.

Сохранить точную цену, большой ID и отсутствие значения

Экспорт учёта должен сохранить точную цену и длинный внешний идентификатор. Если привести все похожие на число строки к float, можно изменить значимые цифры. Получим отрицательную корректировку, unsigned остаток, DECIMAL цену, большой ID и NULL — с их фактическими типами PHP.

Путь данных этой программы
  1. adjustment=-2 → int, signed true
  2. stock=4 → int, signed false
  3. DECIMAL(12,2) → строка 1234567890.12
  4. BIGINT UNSIGNED → строка 18446744073709551615
  5. SQL NULL → PHP null, не ноль и не пустая строка

Сначала короткое действие

$columns = $result->getColumns();
$row = $result->fetchOne();
echo $columns[2]->getFractionalDigits(); // 2
echo $row['price'];                     // 1234567890.12
echo get_debug_type($row['price']);      // string

DECIMAL(12,2) задаёт всего 12 десятичных цифр, из них 2 после точки. Это точное SQL-значение; возвращённая строка сохраняет его написание без промежуточного float. BIGINT UNSIGNED допускает число больше максимального PHP int в этом 64-битном профиле, поэтому ID тоже остаётся строкой. isNumberSigned описывает допустимый знак типа, а не проверяет, отрицательна ли текущая ячейка. getType сравнивается с MYSQLX_TYPE_*; get_debug_type читает тип уже полученного PHP-значения.

Что получится

НаблюдениеРезультат
Корректировка-2
Тип PHP корректировкиint
Корректировка signedда
Остаток4
Остаток signedнет
Точная цена1234567890.12
Тип PHP ценыstring
Дробных знаков2
Внешний ID18446744073709551615
Тип PHP внешнего IDstring
ПримечаниеNULL: значение не задано
Тип PHP примечанияnull
Самостоятельный файл и архив для запуска
Сохранить точную цену, большой ID и отсутствие значенияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/metadata.php';

$config = x142Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x142OwnTable($session,$schema,$owned,'numbers',
        '(adjustment INT NOT NULL, stock INT UNSIGNED NOT NULL, price DECIMAL(12,2) NOT NULL, '
        .'external_id BIGINT UNSIGNED NOT NULL, note VARCHAR(20) NULL) ENGINE=InnoDB');
    $qualified = x142Qualified($schema,$name);
    $session->sql('INSERT INTO '.$qualified.' (adjustment,stock,price,external_id,note) '
        .'VALUES (-2,4,1234567890.12,18446744073709551615,NULL)')->execute();
    $result = $session->sql('SELECT adjustment,stock,price,external_id,note FROM '.$qualified)->execute();
    $columns = $result->getColumns();
    $meta = metadata142Columns($columns);
    $row = $result->fetchOne();
    $rows = [['Корректировка',$row['adjustment']],['Тип PHP корректировки',get_debug_type($row['adjustment'])],
        ['Корректировка signed',$meta[0]['signed']],['Остаток',$row['stock']],['Остаток signed',$meta[1]['signed']],
        ['Точная цена',$row['price']],['Тип PHP цены',get_debug_type($row['price'])],['Дробных знаков',$meta[2]['fractional_digits']],
        ['Внешний ID',$row['external_id']],['Тип PHP внешнего ID',get_debug_type($row['external_id'])],
        ['Примечание',$row['note'] === null ? 'NULL: значение не задано' : $row['note']],
        ['Тип PHP примечания',get_debug_type($row['note'])]];
    $checks = ['signed_value'=>$row['adjustment'] === -2 && $meta[0]['signed'] === true,
        'unsigned_value'=>$row['stock'] === 4 && $meta[1]['signed'] === false,
        'integer_metadata'=>$meta[0]['type'] === MYSQLX_TYPE_INT && $meta[1]['type'] === MYSQLX_TYPE_INT,
        'exact_decimal'=>$row['price'] === '1234567890.12' && $meta[2]['type'] === MYSQLX_TYPE_DECIMAL,
        'decimal_scale'=>$meta[2]['fractional_digits'] === 2,
        'large_id'=>$row['external_id'] === '18446744073709551615' && $meta[3]['type'] === MYSQLX_TYPE_BIGINT,
        'null_preserved'=>array_key_exists('note',$row) && $row['note'] === null,
        'flags_boolean'=>is_bool($meta[0]['signed']) && is_bool($meta[0]['padded']),
        'numeric_charset_absent'=>$meta[2]['charset'] === null && $meta[2]['collation'] === null];
    $observations = ['metadata'=>$meta,'row'=>$row,'php_types'=>array_map('get_debug_type',$row),
        'type_constants'=>['INT'=>MYSQLX_TYPE_INT,'DECIMAL'=>MYSQLX_TYPE_DECIMAL,'BIGINT'=>MYSQLX_TYPE_BIGINT]];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x142Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x142Report('mysql142-x-metadata-numeric-shape', 'Сохранить точную цену, большой ID и отсутствие значения', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Корректировка — -2
Тип PHP корректировки — int
Корректировка signed — да
Остаток — 4
Остаток signed — нет
Точная цена — 1234567890.12
Тип PHP цены — string
Дробных знаков — 2
Внешний ID — 18446744073709551615
Тип PHP внешнего ID — string
Примечание — NULL: значение не задано
Тип PHP примечания — null

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт и удаляет только собственные объекты; metadata.php сохраняет описание колонок, пока жив исходный Result. Откройте result.html и result.json; в последней программе также report.html.

Наблюдения сохранённого запуска · Значения, типы и metadata.

Границы и ошибки

Не используйте JSON_NUMERIC_CHECK для такого экспорта: строковый ID и точная десятичная сумма должны сохранить контракт. Для расчётов задайте единицы и способ точной арифметики отдельно — например, целые копейки в подходящем диапазоне либо BCMath; этот урок не выполняет денежные вычисления через float. NULL отличается от пустого текста и нуля. Metadata числового поля не имеет текстовой кодировки в этом профиле: getters возвращают null, несмотря на краткие string-сигнатуры Manual.

Значение DECIMAL приходит строкой, и возникает желание автоматически превратить каждую такую строку во float. Реальная задача на Stack Overflow. Разделяем SQL-тип, фактический PHP-тип и формат показа. Точную десятичную сумму и большой ID сохраняем строками; числовое оформление не требует потери разрядов.

Попробуйте изменить

Замените NULL в note на пустую строку. Изменится ли SQL-тип столбца?

Ожидаемое решение

Столбец останется VARCHAR(20), но фактическое значение станет string длины 0. Строка отчёта должна отличать пустой текст от отсутствия значения; проверка null_preserved закономерно перестанет подходить.

Различить ширину колонки, символы и байты

При создании формы легко перепутать VARCHAR(12), значение getLength и длину уже введённого текста. Покажем все три вещи рядом: строка «Кот🙂» содержит четыре символа и занимает десять байтов UTF-8, а метаданные VARCHAR(12) этого профиля сообщают ширину 48.

Путь данных этой программы
  1. DDL разрешает 12 символов для label
  2. В строке Кот🙂: 3 кириллических символа и 1 emoji
  3. CHAR_LENGTH даёт 4, OCTET_LENGTH даёт 10
  4. getLength даёт 48 — ширину поля в полученных metadata
  5. CHAR(5) отдельно показывает rightpad; это не команда trim

Сначала короткое действие

$columns = $result->getColumns();
echo $columns[1]->getLength();           // 48
echo $columns[1]->getCharacterSetName(); // utf8mb4
echo $columns[1]->getCollationName();    // utf8mb4_bin

В определении CHAR/VARCHAR число задаётся в символах. В наблюдаемом ответе MySQL 8.4.13 и PECL 8.0.30 getLength равен 5×4 и 12×4: серверная ширина учитывает максимальные 4 байта utf8mb4. Фактическая строка короче. CHAR_LENGTH считает символы SQL, OCTET_LENGTH — байты; визуально один знак может состоять из нескольких Unicode-кодов, поэтому это не универсальный счётчик пользовательских графем. Кодировка задаёт представление, collation — сравнение. Они не заменяют друг друга.

Что получится

НаблюдениеРезультат
Значение CHAR(5)AB
Значение VARCHAR(12)Кот🙂
Ширина metadata CHAR(5)20
Ширина metadata VARCHAR(12)48
Символов в текущем label4
Байтов в текущем label10
Набор символовutf8mb4
Правило сравненияutf8mb4_bin
CHAR: isPaddedда
VARCHAR: isPaddedнет
Самостоятельный файл и архив для запуска
Различить ширину колонки, символы и байтыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/metadata.php';

$config = x142Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x142OwnTable($session,$schema,$owned,'text_width',
        '(code CHAR(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, '
        .'label VARCHAR(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL) ENGINE=InnoDB');
    $qualified = x142Qualified($schema,$name);
    $session->sql('INSERT INTO '.$qualified.' (code,label) VALUES (?,?)')->bind('AB')->bind('Кот🙂')->execute();
    $result = $session->sql('SELECT code,label,CHAR_LENGTH(label) AS characters,OCTET_LENGTH(label) AS bytes FROM '.$qualified)->execute();
    $columns = $result->getColumns();
    $meta = metadata142Columns($columns);
    $row = $result->fetchOne();
    $rows = [['Значение CHAR(5)',$row['code']],['Значение VARCHAR(12)',$row['label']],
        ['Ширина metadata CHAR(5)',$meta[0]['length']],['Ширина metadata VARCHAR(12)',$meta[1]['length']],
        ['Символов в текущем label',$row['characters']],['Байтов в текущем label',$row['bytes']],
        ['Набор символов',$meta[1]['charset']],['Правило сравнения',$meta[1]['collation']],
        ['CHAR: isPadded',$meta[0]['padded']],['VARCHAR: isPadded',$meta[1]['padded']]];
    $checks = ['exact_text'=>$row['code'] === 'AB' && $row['label'] === 'Кот🙂',
        'length_is_metadata'=>$meta[0]['length'] === 20 && $meta[1]['length'] === 48,
        'value_characters'=>$row['characters'] === 4,'value_bytes'=>$row['bytes'] === 10,
        'character_set'=>$meta[0]['charset'] === 'utf8mb4' && $meta[1]['charset'] === 'utf8mb4',
        'collation'=>$meta[0]['collation'] === 'utf8mb4_bin' && $meta[1]['collation'] === 'utf8mb4_bin',
        'padding_flags'=>$meta[0]['padded'] === true && $meta[1]['padded'] === false,
        'text_type'=>$meta[0]['type'] === MYSQLX_TYPE_BYTES && $meta[1]['type'] === MYSQLX_TYPE_BYTES];
    $observations = ['metadata'=>$meta,'row'=>$row,'declared_characters'=>['code'=>5,'label'=>12],
        'php_bytes'=>strlen($row['label']),'type_constant_bytes'=>MYSQLX_TYPE_BYTES];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x142Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x142Report('mysql142-x-metadata-text-width', 'Различить ширину колонки, символы и байты', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Значение CHAR(5) — AB
Значение VARCHAR(12) — Кот🙂
Ширина metadata CHAR(5) — 20
Ширина metadata VARCHAR(12) — 48
Символов в текущем label — 4
Байтов в текущем label — 10
Набор символов — utf8mb4
Правило сравнения — utf8mb4_bin
CHAR: isPadded — да
VARCHAR: isPadded — нет

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт и удаляет только собственные объекты; metadata.php сохраняет описание колонок, пока жив исходный Result. Откройте result.html и result.json; в последней программе также report.html.

Наблюдения сохранённого запуска · Значения, типы и metadata.

Границы и ошибки

Не переносите это соотношение автоматически на числовые, двоичные или вычисленные поля. getLength не является готовым maxlength и не проверяет корректность ввода. В CHAR прочитано AB без добавленных пробелов, хотя isPadded true: флаг metadata и реальное значение — разные наблюдения. Не обрезайте значимые пробелы VARCHAR. Таблица задаёт свою collation; глобальные настройки сервера и sql_mode не меняются.

Ширина поля в metadata отличается от числа в CHAR(n), поэтому её ошибочно принимают за длину текущего значения. Реальная задача на Stack Overflow. Сравниваем объявленный размер, метаданные, CHAR_LENGTH и OCTET_LENGTH на одной строке. Проблема описана для C API: единицы X DevAPI проверяются отдельно, а не переносятся из ответа другого драйвера.

Попробуйте изменить

Замените Кот🙂 на ABCD. Какие наблюдения изменятся?

Ожидаемое решение

Символов останется 4, фактических байтов станет 4. Ширина metadata 48, объявленный VARCHAR(12), кодировка и collation останутся прежними: они описывают поле, а не одно его значение.

Показать заголовки, когда товаров нет

Менеджер ввёл фильтр, который не нашёл ни одного товара. Экран должен сохранить понятные заголовки и сказать, что совпадений нет. Если строить их из array_keys($rows[0]), код обратится к несуществующей строке. Сравним пустые TableSelect и SQL: в проверенном профиле первый сохраняет metadata, второй её не предоставляет. Поэтому заголовки также принадлежат явному контракту экрана.

Путь данных этой программы
  1. В таблице есть только товар id=1
  2. Фильтр id=999 → 0 строк в обоих ответах
  3. TableSelect сохраняет 2 описания; SQL getters дают [], [] и 0
  4. Фиксированная проекция sku/product_name → словарь Артикул/Товар
  5. Пустые данные → сообщение об отсутствии совпадений, не ошибка подключения

Сначала короткое действие

$displayColumns = ['sku'=>'Артикул','product_name'=>'Товар'];
$result = $session->sql('SELECT sku,name AS product_name FROM '.$qualified.' WHERE id=?')
    ->bind(999)->execute();
$names = $result->getColumnNames();
$labels = $names === [] ? array_keys($displayColumns) : $names;
$headers = array_map(static fn(string $label): string => $displayColumns[$label], $labels);
$rows = $result->fetchAll();
if ($rows === []) {
    echo implode(', ', $headers), PHP_EOL;
    echo 'По фильтру товары не найдены';
}

getColumnsCount, getColumnNames и getColumns читают доступное описание результата, а не текст SQL. В связке PECL mysql_xdevapi 8.0.30/MySQL 8.4.13 нулевой TableSelect сохраняет две колонки; такой же SQL SELECT возвращает count=0, names=[] и metadata=[]. Обе выборки данных — []. Словарь $displayColumns заранее соответствует явному SELECT и сохраняет человеческие заголовки. Полная программа отдельно фиксирует отсутствие SQL metadata, выбор fallback и отображаемые заголовки; не подменяет пустую metadata придуманными ColumnResult.

В коротком фрагменте $qualified — доверенное полное имя собственной таблицы: `схема`.`таблица`. Полный файл получает его через x142Qualified после создания таблицы. Получение объекта Schema само по себе не выбирает SQL default database; пользовательский текст в имя не подставляется.

Что получится

НаблюдениеРезультат
Заголовки TableSelectsku, product_name
Колонок TableSelect2
Строк TableSelect0
Имена полей SQLНе предоставлены: []
Колонок SQL0
Строк SQL0
Сообщение экранаПо фильтру товары не найдены
Описаний SQL0
Класс metadata TableSelectmysql_xdevapi\ColumnResult
Заголовки экранаАртикул, Товар
Взяты из контракта проекциида

Экран оставляет заголовки «Артикул» и «Товар», а под ними — «По фильтру товары не найдены». При отсутствии SQL metadata их сохраняет собственный словарь фиксированной проекции; первой строки данных в этом сценарии нет.

Самостоятельный файл и архив для запуска
Показать заголовки, когда товаров нетСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/metadata.php';

$config = x142Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    $name = x142OwnTable($session,$schema,$owned,'empty_screen',
        '(id INT PRIMARY KEY, sku VARCHAR(12) NOT NULL, name VARCHAR(40) NOT NULL) ENGINE=InnoDB');
    $qualified = x142Qualified($schema,$name);
    $table = $schema->getTable($name);
    $session->sql("INSERT INTO ".$qualified." (id,sku,name) VALUES (1,'LED','Светильник')")->execute();
    $rowResult = $table->select('sku','name AS product_name')->where('id = :id')->bind(['id'=>999])->execute();
    $rowColumns = $rowResult->getColumns();
    $rowMeta = metadata142Columns($rowColumns);
    $rowNames = $rowResult->getColumnNames();
    $rowCount = $rowResult->getColumnsCount();
    $rowData = $rowResult->fetchAll();
    $sqlResult = $session->sql('SELECT sku,name AS product_name FROM '.$qualified.' WHERE id=?')->bind(999)->execute();
    $sqlColumns = $sqlResult->getColumns();
    $sqlMeta = metadata142Columns($sqlColumns);
    $sqlNames = $sqlResult->getColumnNames();
    $sqlCount = $sqlResult->getColumnsCount();
    $sqlData = $sqlResult->fetchAll();
    $displayColumns = ['sku'=>'Артикул','product_name'=>'Товар'];
    $expectedLabels = array_keys($displayColumns);
    $fallbackUsed = $sqlNames === [];
    $displayLabels = $fallbackUsed ? $expectedLabels : $sqlNames;
    if ($displayLabels !== $expectedLabels) {
        throw new RuntimeException('Неожиданные поля экрана');
    }
    $headers = array_map(static fn(string $label): string => $displayColumns[$label],$displayLabels);
    $message = $sqlData === [] ? 'По фильтру товары не найдены' : 'Найдены товары';
    $rows = [['Заголовки TableSelect',implode(', ',$rowNames)],['Колонок TableSelect',$rowCount],
        ['Строк TableSelect',count($rowData)],['Имена полей SQL',$sqlNames === [] ? 'Не предоставлены: []' : implode(', ',$sqlNames)],
        ['Колонок SQL',$sqlCount],['Строк SQL',count($sqlData)],['Сообщение экрана',$message],
        ['Описаний SQL',count($sqlMeta)],['Класс metadata TableSelect',$rowMeta[0]['class']],
        ['Заголовки экрана',implode(', ',$headers)],['Взяты из контракта проекции',$fallbackUsed]];
    $checks = ['row_empty'=>$rowData === [],'sql_empty'=>$sqlData === [],
        'row_names_available'=>$rowNames === ['sku','product_name'],
        'row_count_available'=>$rowCount === 2,
        'row_metadata_available'=>count($rowMeta) === 2,
        'sql_names_absent'=>$sqlNames === [],'sql_count_zero'=>$sqlCount === 0,'sql_metadata_absent'=>$sqlMeta === [],
        'row_labels_match'=>array_column($rowMeta,'label') === $expectedLabels,
        'real_class'=>$rowMeta[0]['class'] === 'mysql_xdevapi\\ColumnResult',
        'explicit_fallback'=>$fallbackUsed === true && $displayLabels === $expectedLabels,
        'headers_preserved'=>$headers === ['Артикул','Товар'],
        'clear_empty_message'=>$message === 'По фильтру товары не найдены'];
    $observations = ['row_metadata'=>$rowMeta,'sql_metadata'=>$sqlMeta,'row_rows'=>$rowData,'sql_rows'=>$sqlData,
        'row_names'=>$rowNames,'sql_names'=>$sqlNames,'row_count'=>$rowCount,'sql_count'=>$sqlCount,
        'expected_projection'=>$expectedLabels,'display_labels'=>$displayLabels,'headers'=>$headers,
        'fallback_used'=>$fallbackUsed,'message'=>$message];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x142Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x142Report('mysql142-x-metadata-empty-screen', 'Показать заголовки, когда товаров нет', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Заголовки TableSelect — sku, product_name
Колонок TableSelect — 2
Строк TableSelect — 0
Имена полей SQL — Не предоставлены: []
Колонок SQL — 0
Строк SQL — 0
Сообщение экрана — По фильтру товары не найдены
Описаний SQL — 0
Класс metadata TableSelect — mysql_xdevapi\ColumnResult
Заголовки экрана — Артикул, Товар
Взяты из контракта проекции — да

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт и удаляет только собственные объекты; metadata.php сохраняет описание колонок, пока жив исходный Result. Откройте result.html и result.json; в последней программе также report.html.

Наблюдения сохранённого запуска · Значения, типы и metadata.

Границы и ошибки

Это наблюдаемое различие конкретного профиля, не универсальная гарантия всех MySQL-драйверов. RowResult читает metadata из результата напрямую, а SqlStatementResult в исходнике 8.0.30 ищет её в rowset. Manual обещает пустую metadata для нулевого RowResult, однако именно TableSelect здесь её сохраняет. Не обращайтесь к $sqlMeta[0], если массив пуст. Fallback подходит фиксированной проекции экрана, не произвольному пользовательскому SQL. Ошибка соединения/синтаксиса не должна превращаться в сообщение «товаров нет».

Попробуйте изменить

Измените фильтр на id=1. Какие части результата останутся прежними?

Ожидаемое решение

Договорённые поля sku/product_name и заголовки Артикул/Товар останутся прежними; в обоих массивах данных появится строка. У непустого SQL-ответа появятся доступные metadata, имена и count=2, fallback не потребуется. Проверки, специально ожидающие пустой ответ, нужно заменить проверками нового сценария; сообщение «товаров нет» больше не показывается.

Собрать настоящий HTML-каталог по проверенным колонкам

Теперь результат нужен человеку: открыть каталог и увидеть товар, точную цену, остаток и примечание. Получим только четыре нужных поля и две строки, проверим их структуру и сохраним отдельный report.html. В названии первого товара специально есть <script> и &: они должны читаться как текст. Второй товар показывает разницу между нулём, пустой строкой и SQL NULL.

Путь данных этой программы
  1. SELECT задаёт 4 разрешённых поля и не более 2 строк
  2. metadata проверяет aliases, тип цены и scale 2
  3. словарь интерфейса задаёт надписи и правила показа
  4. каждая реальная строка проходит formatter без float
  5. htmlspecialchars выводит текст ячеек безопасно
  6. report.html открывается как готовый каталог

Сначала короткое действие

$columns = $result->getColumns();
$priceColumn = $columns[1];
$rawRows = $result->fetchAll();
$priceText = str_replace('.', ',', $rawRows[0]['unit_price']);
echo htmlspecialchars($priceText, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); // 12,30

Сначала строка DECIMAL 12.30 становится строкой 12,30 заменой разделителя. Ни арифметики, ни float для этого не нужно. В полном файле metadata подтверждает DECIMAL и два дробных знака, а функция formatter проверяет фактическое значение. Словарь $displayColumns связывает alias с человеческим заголовком и видом ячейки. Вложенный foreach проходит по товарам и выбранным полям; он обрабатывает уже ограниченный SQL-ответ. SQL NULL проверяется раньше пустого текста. Остаток 0 сохраняется как «0», поэтому здесь не применяется empty().

Что получится

НаблюдениеРезультат
Товаров в HTML2
Название первого товараЛампа <script> & свет
Цена без float12,30
Нулевая цена0,00
Нулевой остаток0
SQL NULL— (нет значения)
Пустой текст(пустая строка)
HTML-символы выведены текстомда
Полезный файлreport.html

Сам каталог

ТоварЦена, руб.ОстатокПримечание
Лампа <script> & свет12,304— (нет значения)
Кабель0,000(пустая строка)

Эта таблица показывает ожидаемые ячейки. Открыть отдельный HTML-каталог, полученный программой — полный сохранённый report.html с результатом чтения базы.

Самостоятельный файл и архив для запуска
Собрать настоящий HTML-каталог по проверенным колонкамСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
require __DIR__.'/metadata.php';

$config = x142Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];
    require __DIR__.'/report-renderer.php';
    $name = x142OwnTable($session,$schema,$owned,'report',
        '(id INT PRIMARY KEY, name VARCHAR(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, '
        .'price DECIMAL(10,2) NOT NULL, stock INT UNSIGNED NOT NULL, note VARCHAR(40) NULL) ENGINE=InnoDB');
    $qualified = x142Qualified($schema,$name);
    $session->sql('INSERT INTO '.$qualified.' (id,name,price,stock,note) VALUES (?,?,?,?,?)')
        ->bind(1)->bind('Лампа <script> & свет')->bind('12.30')->bind(4)->bind(null)->execute();
    $session->sql('INSERT INTO '.$qualified.' (id,name,price,stock,note) VALUES (?,?,?,?,?)')
        ->bind(2)->bind('Кабель')->bind('0.00')->bind(0)->bind('')->execute();
    $result = $session->sql('SELECT p.name AS product_name,p.price AS unit_price,p.stock AS stock,p.note AS note '
        .'FROM '.$qualified.' AS p ORDER BY p.id LIMIT 2')->execute();
    $displayColumns = [
        'product_name'=>['title'=>'Товар','kind'=>'text'],
        'unit_price'=>['title'=>'Цена, руб.','kind'=>'money'],
        'stock'=>['title'=>'Остаток','kind'=>'integer'],
        'note'=>['title'=>'Примечание','kind'=>'text'],
    ];
    $columns = $result->getColumns();
    $meta = metadata142Columns($columns);
    $names = $result->getColumnNames();
    if ($names !== array_keys($displayColumns) || $result->getColumnsCount() !== count($displayColumns)) {
        throw new RuntimeException('Изменилась структура отчёта');
    }
    $priceColumn = $columns[1];
    if ($priceColumn->getType() !== MYSQLX_TYPE_DECIMAL || $priceColumn->getFractionalDigits() !== 2) {
        throw new RuntimeException('Изменился тип или точность цены');
    }
    $rawRows = $result->fetchAll();
    $reportColumns = array_column($displayColumns,'title');
    $reportRows = [];
    foreach ($rawRows as $rawRow) {
        $displayRow = [];
        foreach ($displayColumns as $label=>$settings) {
            $displayRow[] = metadata142Display($rawRow[$label],$settings['kind']);
        }
        $reportRows[] = $displayRow;
    }
    $html = metadata142Html($reportColumns,$reportRows);
    if (file_put_contents(__DIR__.'/report.html',$html) === false) {
        throw new RuntimeException('Не удалось записать report.html');
    }
    $expectedReportRows = [
        ['Лампа <script> & свет','12,30','4','— (нет значения)'],
        ['Кабель','0,00','0','(пустая строка)'],
    ];
    $rows = [['Товаров в HTML',count($reportRows)],['Название первого товара',$reportRows[0][0]],
        ['Цена без float',$reportRows[0][1]],['Нулевая цена',$reportRows[1][1]],
        ['Нулевой остаток',$reportRows[1][2]],['SQL NULL',$reportRows[0][3]],
        ['Пустой текст',$reportRows[1][3]],['HTML-символы выведены текстом',!str_contains($html,'<script>')],
        ['Полезный файл','report.html']];
    $checks = ['exact_report_rows'=>$reportRows === $expectedReportRows,
        'header_labels'=>$reportColumns === ['Товар','Цена, руб.','Остаток','Примечание'],
        'raw_values'=>$rawRows === [
            ['product_name'=>'Лампа <script> & свет','unit_price'=>'12.30','stock'=>4,'note'=>null],
            ['product_name'=>'Кабель','unit_price'=>'0.00','stock'=>0,'note'=>'']],
        'real_origins'=>$meta[0]['schema'] === $config['schema'] && $meta[0]['table_name'] === $name && $meta[0]['name'] === 'name',
        'money_string'=>get_debug_type($rawRows[0]['unit_price']) === 'string',
        'zero_preserved'=>$reportRows[1][2] === '0',
        'null_and_empty_distinct'=>$reportRows[0][3] !== $reportRows[1][3],
        'escaped_payload'=>str_contains($html,'Лампа &lt;script&gt; &amp; свет') && !str_contains($html,'<script>'),
        'file_written'=>is_file(__DIR__.'/report.html') && file_get_contents(__DIR__.'/report.html') === $html];
    $observations = ['metadata'=>$meta,'column_names'=>$names,'raw_rows'=>$rawRows,
        'report_columns'=>$reportColumns,'report_rows'=>$reportRows,'report_file'=>'report.html'];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x142Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x142Report('mysql142-x-metadata-rendered-report', 'Собрать настоящий HTML-каталог по проверенным колонкам', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Товаров в HTML — 2
Название первого товара — Лампа <script> & свет
Цена без float — 12,30
Нулевая цена — 0,00
Нулевой остаток — 0
SQL NULL — — (нет значения)
Пустой текст — (пустая строка)
HTML-символы выведены текстом — да
Полезный файл — report.html

Выполните PHP-файл в отдельной лаборатории по README. lab.php создаёт и удаляет только собственные объекты; metadata.php сохраняет описание колонок, пока жив исходный Result. Откройте result.html и result.json; в последней программе также report.html.

Наблюдения сохранённого запуска · Значения, типы и metadata.

Границы и ошибки

Это formatter конкретного отчёта: он не выбирает валюту из SQL-типа и не обещает автоматически показать любой SQL-тип. Для денежных расчётов требуется отдельно выбранная точная арифметика, для локализации — правила приложения. HTML-экранирование выполняется в момент вывода текста, а SQL bind — в момент передачи значений серверу: одно не заменяет другое. Metadata не используется для построения произвольного UPDATE. Таблица прокручивается внутри контейнера на узком экране; исходный JSON сохраняет реальные типы и происхождение колонок.

Попробуйте изменить

Добавьте третьему товару примечание «Проверить <упаковку>». Что должно появиться в HTML и JSON?

Ожидаемое решение

Сначала увеличьте SQL LIMIT до нужного размера и добавьте строку. В JSON останется исходный текст с угловыми скобками. В HTML-коде будут &lt; и &gt;, а человек увидит буквальные <упаковку>. Новое значение не должно становиться HTML-тегом; перечень четырёх колонок остаётся прежним.

Выбрать описание колонки под задачу

APIДля чего
ColumnResultПонять, что означает столбец отчёта, до чтения его значений.
RowResult::getColumnsПолучить описание колонок выбранного через TableSelect экрана.
SqlStatementResult::getColumnsПолучить описание колонок обычного SQL SELECT.
SqlStatementResult::getColumnsCountУзнать ширину результата независимо от количества товаров.
SqlStatementResult::getColumnNamesПолучить ключи полей SQL-ответа в порядке проекции.
ColumnResult::getColumnLabelУзнать имя поля именно в полученном ответе.
ColumnResult::getColumnNameПрочитать исходное имя поля до назначения псевдонима.
ColumnResult::getTableNameУзнать исходную таблицу прямого поля.
ColumnResult::getTableLabelУзнать псевдоним таблицы, использованный запросом.
ColumnResult::getSchemaNameУзнать схему происхождения прямого поля результата.
ColumnResult::getTypeРазличить тип поля в протоколе от значения и типа переменной PHP.
ColumnResult::isNumberSignedПонять, допускает ли числовой столбец отрицательные значения.
ColumnResult::getFractionalDigitsПрочитать объявленную дробную точность числового результата.
ColumnResult::getLengthПрочитать описанную сервером ширину поля, не длину текущей строки.
ColumnResult::getCharacterSetNameУзнать набор символов поля результата.
ColumnResult::getCollationNameУзнать правило сравнения, связанное с полем результата.
ColumnResult::isPaddedУзнать, обозначено ли правое дополнение поля в metadata.

От формы ответа к предупреждениям и квитанциям

Теперь известны заголовки, происхождение и типы выбранных полей; можно показать настоящий отчёт и корректный пустой экран. Следующий шаг — отличить успешную операцию, предупреждение и следующий набор результатов. Для запросов под экран вернитесь к проекции и ограничению данных, а к чтению обычных строк — в каталог через таблицы и SQL.

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

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

mysql_xdevapi\ColumnResultПонять, что означает столбец отчёта, до чтения его значений.

Класс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11. · mysql_xdevapi

class mysql_xdevapi\ColumnResult

Понять, что означает столбец отчёта, до чтения его значений.

Как применяется вызов
  1. SELECT name AS product_name
  2. Result хранит описание столбца и строки
  3. getColumns()[0] → ColumnResult
  4. label product_name связывается с ключом строки; name объясняет происхождение

Запись, вход и результат

class mysql_xdevapi\ColumnResult

Класс получают из результата, он не принимает данные товара для сохранения.

Возвращаемое значение. Один объект описывает один столбец конкретного результата: имя и псевдоним, происхождение, тип, длину и другие свойства. Это не товар и не вся таблица базы.

Два применения

Подписать одну колонку каталога.

$result = $table->select('name AS product_name')->execute();
$columns = $result->getColumns();
echo $columns[0]->getColumnLabel();

Исходные данные, полный запуск и результат.

Разобрать происхождение полей после JOIN.

$columns = $result->getColumns();
foreach ($columns as $column) {
    echo $column->getColumnLabel(), ' ← ', $column->getColumnName(), PHP_EOL;
}

Исходные данные, полный запуск и результат.

Условия и ограничения

Метаданные описывают проекцию запроса, не всю DDL-схему. Они не доказывают право записи, уникальность строк или смысл денег. В 8.0.30 ColumnResult содержит ссылку на внутренние данные результата: держите исходный $result живым, пока вызываете getters. Для дальнейшей работы сохраните обычные строки/числа metadata в массив. Не получайте объекты из временного execute()->getColumns(), если Result сразу уничтожается.

Объект создаёт расширение при чтении описания столбцов. Приложение получает его через getColumns, а не через new ColumnResult. Конструктор закрыт в реализации 8.0.30; отдельный узел официального оглавления сохранён ниже.

class mysql_xdevapi\ColumnResult {
    public function getCharacterSetName(): string;
    public function getCollationName(): string;
    public function getColumnLabel(): string;
    public function getColumnName(): string;
    public function getFractionalDigits(): int;
    public function getLength(): int;
    public function getSchemaName(): string;
    public function getTableLabel(): string;
    public function getTableName(): string;
    public function getType(): int;
    public function isNumberSigned(): int;
    public function isPadded(): int;
}

Это полный состав методов официального обзора, а не исполняемое повторное определение встроенного класса. В краткой документации isNumberSigned/isPadded указаны как int, хотя описание обещает true и реализация возвращает bool. Эти расхождения разобраны в соответствующих карточках.

Версии. Класс указан в PHP Manual начиная с PECL mysql_xdevapi 8.0.11.

mysql_xdevapi\RowResult::getColumnsПолучить описание колонок выбранного через TableSelect экрана.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

RowResult::getColumns(): array

Получить описание колонок выбранного через TableSelect экрана.

Как применяется вызов
  1. TableSelect выбирает sku и name AS product_name
  2. execute возвращает RowResult
  3. getColumns описывает 2 колонки
  4. fetchAll отдельно читает значения товаров

Запись, вход и результат

RowResult::getColumns(): array

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Массив описаний столбцов проекции. Manual называет объекты FieldMetadata и обещает [] при пустом наборе строк. В проверяемой связке PECL 8.0.30 и MySQL 8.4.13 TableSelect возвращает ColumnResult и сохраняет описания при нуле строк. У SqlStatementResult пустой SELECT ведёт себя иначе; оба пути показаны отдельно.

Два применения

Сопоставить идентификатор и название товара с их описаниями.

$result = $table->select('id','name AS product_name')->execute();
$columns = $result->getColumns();

Исходные данные, полный запуск и результат.

Оставить заголовки при отсутствии товаров, не обращаться к $rows[0].

$result = $table->select('sku','name AS product_name')->where('id = :id')->bind(['id'=>999])->execute();
$columns = $result->getColumns();
$rows = $result->fetchAll();

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Получение metadata не является проверкой непустой выдачи. Не копируйте разрушительный DROP DATABASE из демонстрации Manual: наши архивы создают собственные объекты в готовой лаборатории и удаляют только их. Не объявляйте FieldMetadata и ColumnResult взаимозаменяемыми классами без проверки используемого расширения.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

mysql_xdevapi\SqlStatementResult::getColumnsПолучить описание колонок обычного SQL SELECT.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

SqlStatementResult::getColumns(): array

Получить описание колонок обычного SQL SELECT.

Как применяется вызов
  1. SQL JOIN выбирает товар и категорию
  2. execute возвращает SqlStatementResult
  3. getColumns сохраняет порядок SELECT
  4. исходные имена и aliases объясняют каждый столбец

Запись, вход и результат

SqlStatementResult::getColumns(): array

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Массив объектов описания результата. Страница Manual не заполняет семантику return. В непустом SELECT выбранного профиля это ColumnResult; при нуле строк SQL getters в этом профиле не предоставляют metadata и возвращают пустые массивы.

Два применения

Разобрать два поля name из разных таблиц.

$result = $session->sql($sql)->execute();
$columns = $result->getColumns();

Исходные данные, полный запуск и результат.

Сверить ожидаемые поля перед созданием HTML-отчёта.

$columns = $result->getColumns();
$labels = array_map(static fn($column) => $column->getColumnLabel(), $columns);

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Объекты читаются, пока существует их Result. При пустом SQL-ответе PECL 8.0.30/MySQL 8.4.13 не обращайтесь к $columns[0]: массива описаний может не быть. Для пустого экрана держите собственный контракт выбранных полей и заголовков. Не используйте произвольные имена из metadata для генерации новых SQL-команд. Для вычисления может отсутствовать физическая таблица происхождения.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

mysql_xdevapi\SqlStatementResult::getColumnsCountУзнать ширину результата независимо от количества товаров.

В PECL 8.0.14 getColumnCount переименован в getColumnsCount. Исторический URL официальной страницы остаётся getcolumncount; вызывайте существующее имя с s. · mysql_xdevapi

SqlStatementResult::getColumnsCount(): int

Узнать ширину результата независимо от количества товаров.

Как применяется вызов
  1. SELECT sku, name AS product_name задаёт 2 поля
  2. Фильтр оставляет 0 строк
  3. SQL getter в этом профиле даёт 0: metadata не предоставлена
  4. Два заголовка пустого экрана берём из собственного контракта проекции

Запись, вход и результат

SqlStatementResult::getColumnsCount(): int

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Число колонок; 0, если колонок нет. Это число полей каждой строки, а не число строк результата.

Два применения

Показать фактическое отсутствие SQL metadata отдельно от нуля найденных товаров.

$columnCount = $sqlResult->getColumnsCount();
$rows = $sqlResult->fetchAll();

Исходные данные, полный запуск и результат.

Обнаружить расхождение фиксированного renderer и запроса.

if ($result->getColumnsCount() !== count($displayColumns)) {
    throw new RuntimeException('Изменилась структура отчёта');
}

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Проверка count не заменяет проверку имён: два разных SELECT могут иметь одинаковую ширину. В пустом SQL SELECT связки PECL 8.0.30/MySQL 8.4.13 getter возвращает 0, хотя текст запроса перечисляет два поля: metadata текущего SQL rowset не предоставлена. Это не число товаров. COUNT(*) в SQL возвращает значение в одной колонке; не смешивайте два механизма.

Версии. В PECL 8.0.14 getColumnCount переименован в getColumnsCount. Исторический URL официальной страницы остаётся getcolumncount; вызывайте существующее имя с s.

mysql_xdevapi\SqlStatementResult::getColumnNamesПолучить ключи полей SQL-ответа в порядке проекции.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

SqlStatementResult::getColumnNames(): array

Получить ключи полей SQL-ответа в порядке проекции.

Как применяется вызов
  1. name AS product_name задаёт имя результата
  2. непустой ответ сообщает product_name в getColumnNames
  3. пустой SQL в этом профиле возвращает []
  4. собственный словарь сохраняет заголовки без первой строки

Запись, вход и результат

SqlStatementResult::getColumnNames(): array

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Массив имён результата, включая aliases. Не перечень исходных колонок всей таблицы; подробный return в Manual не заполнен.

Два применения

При отсутствии SQL metadata взять известные имена из контракта проекции.

$names = $sqlResult->getColumnNames();
$labels = $names === [] ? array_keys($displayColumns) : $names;
$rows = $sqlResult->fetchAll();

Исходные данные, полный запуск и результат.

Сверить точный порядок разрешённых колонок.

if ($result->getColumnNames() !== array_keys($displayColumns)) {
    throw new RuntimeException('Неожиданные поля отчёта');
}

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Пустой массив не следует превращать в выдуманные технические metadata: fallback хранится отдельно как контракт приложения. Названия для человека выбирайте в собственном словаре: alias product_name полезен как ключ данных, но надпись «Товар» управляется интерфейсом. Одинаковые aliases создают неоднозначность ассоциативной строки, поэтому задавайте уникальные имена до execute.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

mysql_xdevapi\ColumnResult::getColumnLabelУзнать имя поля именно в полученном ответе.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getColumnLabel(): string

Узнать имя поля именно в полученном ответе.

Как применяется вызов
  1. В таблице name
  2. SELECT name AS product_name
  3. label product_name
  4. значение доступно как $row[product_name]

Запись, вход и результат

ColumnResult::getColumnLabel(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Метка колонки результата: при явном AS — заданный псевдоним. Реализация читает поле metadata name.

Два применения

Соединить описание заголовка с фактическим значением.

$label = $columns[1]->getColumnLabel();
$value = $row[$label];

Исходные данные, полный запуск и результат.

Различить product_name и category_name после JOIN.

foreach ($columns as $column) {
    echo $column->getColumnLabel(), PHP_EOL;
}

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Label не обязательно совпадает с исходным именем и не является безопасным HTML сам по себе. Даже доверенный технический alias при выводе проходит экранирование. Для происхождения нужен getColumnName, а не попытка вырезать часть псевдонима.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getColumnNameПрочитать исходное имя поля до назначения псевдонима.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getColumnName(): string

Прочитать исходное имя поля до назначения псевдонима.

Как применяется вызов
  1. products.name AS product_name
  2. getColumnLabel → product_name
  3. getColumnName → name
  4. computed label может не иметь одного исходного name

Запись, вход и результат

ColumnResult::getColumnName(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Исходное имя из original_name metadata. Для выражения исходная физическая колонка может отсутствовать: пустая строка не является именем несуществующей таблицы.

Два применения

Объяснить, какое поле переименовано для экрана.

echo $columns[1]->getColumnName(), ' → ', $columns[1]->getColumnLabel();

Исходные данные, полный запуск и результат.

Не приписывать вычисленному результату выдуманный столбец.

$original = $column->getColumnName();
$originText = $original === '' ? 'Вычисленное поле' : $original;

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Одинаковое name может принадлежать разным таблицам. Для полного происхождения сопоставляйте schema, table name и column name; для обращения к PHP-массиву используйте label. Метаданные запроса не являются полной моделью зависимостей сложного выражения.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getTableNameУзнать исходную таблицу прямого поля.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getTableName(): string

Узнать исходную таблицу прямого поля.

Как применяется вызов
  1. FROM products AS p
  2. p.name AS product_name
  3. table label p
  4. table name — фактическое имя созданной таблицы

Запись, вход и результат

ColumnResult::getTableName(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Имя таблицы колонки; в реализации берётся original_table до применения table alias.

Два применения

Понять, откуда пришло имя товара в JOIN.

$sourceTable = $columns[0]->getTableName();

Исходные данные, полный запуск и результат.

Проверить происхождение прямого поля в ограниченном отчёте.

$fromExpectedTable = $column->getTableName() === $name;

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Выражение или сложная проекция не обязаны иметь одну исходную таблицу. Пустое значение следует показать как отсутствие данного атрибута, а не подставлять последнюю увиденную таблицу. Это информация о результате, не разрешение UPDATE.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getTableLabelУзнать псевдоним таблицы, использованный запросом.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getTableLabel(): string

Узнать псевдоним таблицы, использованный запросом.

Как применяется вызов
  1. Одна таблица названа p, другая c
  2. p.name и c.name имеют одинаковые исходные имена
  3. table label отличает p от c
  4. column label отличает product_name от category_name

Запись, вход и результат

ColumnResult::getTableLabel(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Метка таблицы из metadata table; при AS обычно отличается от original_table.

Два применения

Показать p.name рядом с c.name.

echo $columns[0]->getTableLabel(), '.', $columns[0]->getColumnName();

Исходные данные, полный запуск и результат.

Сопоставить прямой столбец с известной ролью таблицы в конкретном JOIN.

$isCategoryColumn = $column->getTableLabel() === 'c';

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Alias локален запросу и не переименовывает таблицу на сервере. Не используйте его как универсальный физический адрес; для этого существуют getTableName и getSchemaName. У вычисленного поля table label может быть пустым.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getSchemaNameУзнать схему происхождения прямого поля результата.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getSchemaName(): string

Узнать схему происхождения прямого поля результата.

Как применяется вызов
  1. Учебная схема содержит две таблицы
  2. JOIN использует обе с полными именами
  3. прямые поля сообщают выбранную схему
  4. вычисление не обязано сообщать схему

Запись, вход и результат

ColumnResult::getSchemaName(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Имя схемы, где хранится исходная колонка; реализация возвращает поле schema metadata.

Два применения

Проверить, что прямое поле пришло из выбранной лаборатории.

$sameSchema = $columns[0]->getSchemaName() === $config['schema'];

Исходные данные, полный запуск и результат.

Сохранить техническое происхождение поля в JSON отчёта.

$origin = [$column->getSchemaName(),$column->getTableName(),$column->getColumnName()];

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Имя схемы не является идентификатором клиента для проверки доступа. Не показывайте внутренние имена БД на публичном сайте без предметной необходимости; учебный JSON сохраняет только собственные объекты. Отсутствие schema у вычисленного поля не означает потерю соединения.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getTypeРазличить тип поля в протоколе от значения и типа переменной PHP.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getType(): int

Различить тип поля в протоколе от значения и типа переменной PHP.

Как применяется вызов
  1. DECIMAL цена, INT остаток, BIGINT номер
  2. getType описывает колонку
  3. get_debug_type описывает конкретное прочитанное значение
  4. renderer сохраняет десятичную строку и большой ID

Запись, вход и результат

ColumnResult::getType(): int

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Числовой код типа, отображённый расширением в MYSQLX_TYPE_*; это не строка SQL DDL и не результат get_debug_type(value).

Два применения

Отличить точную десятичную цену от количества.

$isDecimal = $columns[2]->getType() === MYSQLX_TYPE_DECIMAL;

Исходные данные, полный запуск и результат.

Сверить технический тип с заранее заданной ролью столбца.

$type = $column->getType();
$kind = $displayColumns[$column->getColumnLabel()]['kind'];

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Сравнивайте с константами реально загруженного расширения, не с угаданными числами. Код X Protocol и отображённый код MYSQLX_TYPE_* — разные шкалы. Тип сам по себе не сообщает валюту, единицы измерения и правила округления. SQL NULL в конкретной ячейке не превращает объявленный DECIMAL-столбец в отдельный бизнес-тип.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::isNumberSignedПонять, допускает ли числовой столбец отрицательные значения.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::isNumberSigned(): int

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

Как применяется вызов
  1. adjustment INT допускает -2
  2. stock INT UNSIGNED хранит 4
  3. флаг signed различает их диапазоны
  4. текущий положительный элемент не меняет описание колонки

Запись, вход и результат

ColumnResult::isNumberSigned(): int

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Manual в сигнатуре пишет int и в return — true для signed. Реализация 8.0.30 возвращает bool: true для знакового числа, false для unsigned либо нечисловой категории.

Два применения

Различить корректировку и остаток.

$signedAdjustment = $columns[0]->isNumberSigned();
$unsignedStock = $columns[1]->isNumberSigned();

Исходные данные, полный запуск и результат.

Сохранить диагностический флаг вместе с типом, не трактуя любой false как unsigned int.

$signed = $column->isNumberSigned();
$numericType = $column->getType();

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Это признак типа, не проверка value < 0. false у текста не доказывает, что в ячейке целое число. Проверяйте тип и предметный диапазон; metadata не заменяет валидацию пользовательского количества.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getFractionalDigitsПрочитать объявленную дробную точность числового результата.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getFractionalDigits(): int

Прочитать объявленную дробную точность числового результата.

Как применяется вызов
  1. price DECIMAL(12,2)
  2. значение 1234567890.12
  3. metadata fractional_digits 2
  4. значение сохраняется строкой без промежуточного float

Запись, вход и результат

ColumnResult::getFractionalDigits(): int

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Число дробных десятичных знаков в metadata. У DECIMAL(12,2) scale равен 2; это описание столбца, а не подсчёт символов после точки в произвольной строке.

Два применения

Убедиться, что отчёт получает цену с двумя десятичными знаками.

$scale = $columns[2]->getFractionalDigits();

Исходные данные, полный запуск и результат.

Обнаружить изменение контракта конкретного денежного отчёта.

if ($priceColumn->getFractionalDigits() !== 2) {
    throw new RuntimeException('Изменилась точность цены');
}

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Не округляйте автоматически любой numeric столбец до этого количества без знания задачи. Для FLOAT/DOUBLE метаданные не превращают приближённое хранение в точное. Для вычисленных выражений тип и scale могут отличаться от исходных колонок; читайте описание именно результата.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getLengthПрочитать описанную сервером ширину поля, не длину текущей строки.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getLength(): int

Прочитать описанную сервером ширину поля, не длину текущей строки.

Как применяется вызов
  1. Колонка VARCHAR(12)
  2. в ячейке короткая кириллица и emoji
  3. metadata length описывает возможность поля
  4. фактические символы и байты вычисляются для значения отдельно

Запись, вход и результат

ColumnResult::getLength(): int

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Значение length из metadata поля. Интерпретация зависит от типа и протокола; рядом в примере отдельно показываются объявленная ширина, CHAR_LENGTH и OCTET_LENGTH.

Два применения

Не растягивать поле ввода по текущему короткому слову.

$declaredMetadataLength = $columns[1]->getLength();

Исходные данные, полный запуск и результат.

Сохранить числовую ширину отдельно от дробной точности.

$numberWidth = $columns[2]->getLength();
$scale = $columns[2]->getFractionalDigits();

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Нельзя универсально присвоить это значение HTML maxlength или считать его strlen(value). В исходнике 8.0.30 на 32-битном PHP длина сверх PHP_INT_MAX может возвращаться double, несмотря на краткий int в Manual; наш профиль 64-битный. Metadata не заменяет явные ограничения ввода, SQL-тип и корректную Unicode-валидацию.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getCharacterSetNameУзнать набор символов поля результата.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getCharacterSetName(): string

Узнать набор символов поля результата.

Как применяется вызов
  1. Название товара содержит кириллицу и emoji
  2. колонка использует utf8mb4
  3. getCharacterSetName сообщает кодировку результата
  4. длина байтов отличается от числа символов

Запись, вход и результат

ColumnResult::getCharacterSetName(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Имя character set по collation-id metadata. Исходник 8.0.30 может вернуть null, если mysqlnd не распознал этот идентификатор; краткая сигнатура Manual этого не показывает.

Два применения

Объяснить, почему текст допускает emoji и занимает несколько байтов на символ.

$charset = $columns[1]->getCharacterSetName();

Исходные данные, полный запуск и результат.

Сохранить диагностическое отсутствие имени без придумывания UTF-8.

$charset = $column->getCharacterSetName();
$charsetLabel = $charset ?? 'Не распознана';

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Getter ничего не перекодирует и не исправляет повреждённый ввод. Кодировка соединения, хранение в колонке и HTML charset должны быть согласованы отдельно. Для бинарного поля смысл отличается от текста; нельзя автоматически применять текстовые операции к любым BYTES.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::getCollationNameУзнать правило сравнения, связанное с полем результата.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::getCollationName(): string

Узнать правило сравнения, связанное с полем результата.

Как применяется вызов
  1. utf8mb4 задаёт представление символов
  2. utf8mb4_bin задаёт выбранное правило сравнения
  3. metadata показывает правило поля
  4. текст значения при этом остаётся тем же

Запись, вход и результат

ColumnResult::getCollationName(): string

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Имя collation по её идентификатору; реализация может дать null, если идентификатор не найден в таблице mysqlnd.

Два применения

Зафиксировать настройки текстовой колонки отчёта.

$collation = $columns[1]->getCollationName();

Исходные данные, полный запуск и результат.

Сохранить технические сведения для разбора отличий сортировки.

$diagnostics[$column->getColumnLabel()] = $column->getCollationName();

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Collation и character set — разные свойства. Имя правила не заменяет ORDER BY и не задаёт порядок строк само по себе. Не меняйте глобальную конфигурацию сервера ради чтения metadata; учебные таблицы задают нужную collation собственным определением.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

mysql_xdevapi\ColumnResult::isPaddedУзнать, обозначено ли правое дополнение поля в metadata.

Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах. · mysql_xdevapi

ColumnResult::isPadded(): int

Узнать, обозначено ли правое дополнение поля в metadata.

Как применяется вызов
  1. CHAR и VARCHAR имеют разные правила хранения
  2. описание поля может содержать rightpad
  3. isPadded читает флаг
  4. полученная строка проверяется отдельно, пробелы не добавляются наугад

Запись, вход и результат

ColumnResult::isPadded(): int

Методы этой карточки вызываются без параметров.

Возвращаемое значение. Manual показывает int, а описание и реализация 8.0.30 — bool. true означает BYTES_RIGHTPAD у поля категории BYTES; false — отсутствие этой комбинации.

Два применения

Сравнить описание фиксированной и переменной текстовой колонки.

$fixedPadded = $columns[0]->isPadded();
$variablePadded = $columns[1]->isPadded();

Исходные данные, полный запуск и результат.

Отделить характеристику поля от реального содержимого ячейки.

$padded = $column->isPadded();
$rawValue = $row[$column->getColumnLabel()];

Исходные данные, полный запуск и результат.

Условия и ограничения

Параметров нет. Флаг не равен str_ends_with(value," ") и не является указанием безусловно применять trim: значимые пробелы могут быть частью VARCHAR. Также это не PAD SPACE/NO PAD атрибут collation. Для двоичных данных возможен другой заполняющий байт; UI не должен самостоятельно дополнять строку до metadata length без собственного контракта.

Версии. Страница метода не указывает первую версию. Здесь рассматривается PECL mysql_xdevapi 8.0.30; версия PHP и сервера записана в результатах.

Описание объекта: ColumnResult. Соседние свойства полезно читать вместе, сохраняя исходный Result до последнего getter-вызова.

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

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

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

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

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