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

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

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

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

Глава 125 · После подключения X Protocol и основ SQL

MySQL X DevAPI: обычные таблицы, каталог и результаты SQL

От CREATE TABLE и двух товаров до ограниченной страницы: Table, TableInsert, TableSelect, RowResult и подготовленный SQL с понятным чтением результата.

Показать каталог из обычной SQL-таблицы

Нужно показать каталог: у светильника LED цена 1200 рублей и остаток 4, у кабеля CABLE — 300 рублей и остаток 10. Из большого каталога экран должен получить нужные товары и поля, а не всю базу для последующей сортировки в PHP. Здесь пройдём от создания обычной SQL-таблицы до ограниченной страницы и квитанции записи через уже знакомый X Protocol сеанс.

Что хранит сервер, что получает PHP
  1. MySQL: таблица с одинаковыми столбцами для всех товаров
  2. Session → Schema → Table: объекты PHP адресуют эту таблицу
  3. where / orderby / limit: сервер выбирает строки экрана
  4. select: сервер возвращает только нужные колонки
  5. RowResult → массив → понятная HTML-таблица

Таблица похожа на ведомость: столбец price_cents имеет один смысл и единицу измерения во всех строках, а id отличает товары. Это модель данных, не обещание физического расположения строк в файле. Без ORDER BY порядок выдачи не задан. Таблицы, ключи и связи объясняют модель подробнее; внешний ключ связывает строку заказа с существующим покупателем. В этой главе сначала одна таблица; JOIN и согласованная запись двух таблиц идут следующим шагом.

Обычная таблица хранит объявленные колонки, JSON-коллекция хранит документы. Для таблиц X DevAPI предоставляет Table и SQL, для коллекций — Collection. Это разные способы работы на одном Session; замена API сама по себе не перестраивает схему и не делает любой запрос быстрым. Если X Protocol вашему приложению не требуется, существующие главы PDO/MySQLi остаются подходящим путём.

Первое действие: создать известную схему

CREATE TABLE создаёт пустую таблицу. Скобки содержат определения колонок; запятые разделяют их. INT хранит целое число, VARCHAR(32) — строку ограниченной длины; PRIMARY KEY идентифицирует строку, UNIQUE запрещает повтор артикула, NOT NULL не разрешает отсутствие значения. DEFAULT 0 задаёт остаток, если колонку не передали при вставке. AUTO_INCREMENT поручает серверу назначить id; это не обещание последовательности без пропусков.

$session->sql('CREATE TABLE `php_manual_x139`.`products` (
    id INT PRIMARY KEY AUTO_INCREMENT,
    sku VARCHAR(32) NOT NULL UNIQUE,
    name VARCHAR(80) NOT NULL,
    price_cents INT UNSIGNED NOT NULL,
    stock INT UNSIGNED NOT NULL DEFAULT 0
) ENGINE=InnoDB')->execute();
$table = $schema->getTable('products');
echo $table->count(); // 0: таблица создана, товаров пока нет

Это короткий фрагмент для уже открытой учебной Schema php_manual_x139; он не является скриптом подключения. Обратные кавычки выделяют имена, точка разделяет имя схемы и таблицы: SQL не выбирает базу автоматически только потому, что ранее получен объект Schema. В самостоятельных архивах ниже вместо постоянного products используется новое имя mysql141_… со случайным суффиксом. lab.php выполняет такой же явный CREATE TABLE, регистрирует владение объектом и удаляет только его в finally. Полная схема учебного каталога дополнительно содержит active и description; первый полный пример задаёт id явно. Постоянную схему сайта создают миграцией один раз, а не при каждом показе каталога.

Как запустить и увидеть результат

Архив каждой программы содержит самостоятельный PHP-файл, lab.php и README.txt. Нужны отдельная учебная схема, PHP 8.3.35 с PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol; настройка описана в подготовке лаборатории. Параметры подключения берутся из PHPMAN_MYSQLX_URI и PHPMAN_MYSQLX_SCHEMA, секреты в архив не включены. README перечисляет права на учебную схему; для VIEW нужны права создания и чтения представления. Операции не работают с таблицами сайта.

После CLI-запуска откройте result.html: увидите те строки, которые прочитаны из MySQL. result.json сохраняет значения, типы и наблюдения, metrics.json — проверки и очистку. Такой запуск проверяет PHP и базу; он не выдаётся за HTTP-проверку формы в браузере. При ошибке соединения или SQL программа завершается ошибкой, а не рисует успешный пустой каталог.

Почему экран не начинается с SELECT *

Звёздочка запрашивает все колонки. Само по себе SELECT * не означает все строки: их число регулируют WHERE и LIMIT. Но для списка, где нужны только id, sku и цена, передача длинного description тратит сеть и память и связывает ответ со всей схемой таблицы. Ещё хуже прочитать сотни тысяч строк, а затем фильтровать и сортировать их массив в PHP. В нашем каталоге where отбирает товары, orderby задаёт порядок, limit/offset выбирают страницу, select задаёт её поля. Небольшая выдача не гарантирует небольшую работу сервера: индексы и план запроса нужно оценивать отдельно. Подробности — проекция и лишние столбцы и запрос под экран.

Короткие фрагменты ниже используют уже подготовленные объекты. Сначала прочитайте ближайший разбор; затем полный файл показывает подключение, данные, действие, результат и завершение. Внутри API-справочника можно сразу перейти от метода к его двум применениям и самостоятельным программам.

Создать обычную таблицу и показать первые два товара

Нужен небольшой каталог: артикул, название, цена и остаток. Здесь каждая строка имеет одинаковые столбцы, а сервер проверяет объявленные типы и уникальность артикула. Получим две видимые строки и квитанцию добавления через тот же Session, который раньше работал с JSON-документами.

Путь данных этой программы
  1. DDL создаёт пустую таблицу с колонками id, sku, name, price_cents, stock
  2. getTable возвращает адрес этой таблицы в объекте Table
  3. insert задаёт порядок колонок; values сопоставляет им значения LED и CABLE
  4. До execute count равен 0; после execute сервер сохранил 2 строки
  5. select читает только четыре поля экрана → foreach формирует строки HTML-ведомости

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

$table = $schema->getTable('products');
$pending = $table->insert('sku','name','price_cents','stock')
    ->values(['LED','Светильник',120000,4]);
$receipt = $pending->execute();

Переменная $table хранит объект доступа, а не весь каталог в памяти PHP. Вызов ->insert перечисляет имена колонок; массив ->values содержит значения в том же порядке. Строки заключены в кавычки, целые числа — без кавычек. execute отправляет подготовленную операцию и возвращает квитанцию. Короткая запись предполагает существующую таблицу с подходящим id по умолчанию; полный файл ниже явно задаёт id и создаёт свою таблицу. В SQL PRIMARY KEY отличает строку по id, UNIQUE запрещает повтор sku, NOT NULL требует значение, DEFAULT задаёт значение пропущенной колонки. Здесь цена измеряется в копейках: 120000 означает 1200 рублей.

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

НаблюдениеРезультат
LEDСветильник; цена 120000 коп.; остаток 4
CABLEКабель; цена 30000 коп.; остаток 10
Строк до execute0
Добавлено строк2
Таблица существуетда
Самостоятельный файл и архив для запуска
Создать обычную таблицу и показать первые два товараСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141OwnTable($session,$schema,$owned,'first_table','(id INT PRIMARY KEY, sku VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(80) NOT NULL, price_cents INT UNSIGNED NOT NULL, stock INT UNSIGNED NOT NULL DEFAULT 0, active TINYINT NOT NULL DEFAULT 1, description TEXT NULL) ENGINE=InnoDB');
    $table = $schema->getTable($name);
    $qualified = x141Qualified($schema,$name);

    $pending = $table->insert('id','sku','name','price_cents','stock')
        ->values([1,'LED','Светильник',120000,4])->values([2,'CABLE','Кабель',30000,10]);
    $beforeExecute = $table->count();
    $receipt = $pending->execute();
    $result = $table->select('sku','name','price_cents','stock')->orderby('id ASC')->execute();
    $saved = $result->fetchAll();
    $rows = [];
    foreach ($saved as $item) {
        $rows[] = [$item['sku'],$item['name'].'; цена '.$item['price_cents'].' коп.; остаток '.$item['stock']];
    }
    $rows[] = ['Строк до execute',$beforeExecute];
    $rows[] = ['Добавлено строк',$receipt->getAffectedItemsCount()];
    $rows[] = ['Таблица существует',$table->existsInDatabase()];
    $checks = ['not_written_early'=>$beforeExecute === 0,
        'receipt_two'=>$receipt->getAffectedItemsCount() === 2,
        'saved_skus'=>array_column($saved,'sku') === ['LED','CABLE'],
        'stored_values'=>$saved[0]['price_cents'] === 120000 && $saved[1]['stock'] === 10,
        'projected_only'=>array_keys($saved[0]) === ['sku','name','price_cents','stock'],
        'table_exists'=>$table->existsInDatabase()];
    $observations = ['table_name'=>$name,'stored_rows'=>$saved,'count_before_execute'=>$beforeExecute,
        'types'=>['handle'=>get_debug_type($table),'pending'=>get_debug_type($pending),
            'receipt'=>get_debug_type($receipt),'select_result'=>get_debug_type($result)]];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-first-table', 'Создать обычную таблицу и показать первые два товара', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
LED — Светильник; цена 120000 коп.; остаток 4
CABLE — Кабель; цена 30000 коп.; остаток 10
Строк до execute — 0
Добавлено строк — 2
Таблица существует — да

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

Table не создаёт таблицу автоматически. Полный архив содержит lab.php: x141OwnTable выполняет показанный CREATE TABLE с фиксированным определением, записывает новое случайное имя и затем возвращает его. Это подготовка лаборатории, а не скрытое создание таблиц в каждом запросе сайта. В приложении схему готовят миграцией. Ошибка прав, отсутствующая таблица или нарушение UNIQUE — отказ, а не пустая выдача. Набор мал для обучения и не доказывает быстродействие; выборка для большого каталога разобрана далее.

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

Добавьте товар STRIP с ценой 50000 копеек и остатком 2. Какие части записи и результата должны измениться?

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

Добавьте очередной values с новым id и sku перед execute. Квитанция станет 3, таблица результата содержит третью строку; схема колонок остаётся прежней. Обновите ожидаемый вывод и проверку списка артикулов.

Узнать таблицу, её схему и представление

При настройке каталога важно понять: код только назвал таблицу или она уже создана? А объект active_products — отдельное хранилище или сохранённый запрос? Получим handle до CREATE, затем используем его после создания и сравним обычную таблицу с VIEW, которое показывает только активные товары.

Путь данных этой программы
  1. Schema + случайное имя → Table handle; таблицы ещё нет
  2. CREATE TABLE создаёт хранилище; прежний handle начинает видеть таблицу
  3. Таблица хранит LED, CABLE, OLD
  4. VIEW содержит SELECT active=1 и при чтении показывает LED/CABLE
  5. getSchema/getSession возвращают владельцев; getTables перечисляет объекты, не их строки

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

$table = $schema->getTable('products');
if ($table->existsInDatabase()) {
    echo $table->getName();
}
$tables = $schema->getTables();

getTable принимает имя и возвращает объект. existsInDatabase делает проверку наличия; сам факт существования PHP-объекта таблицу не создаёт. getTables возвращает массив объектов Table с именами в ключах: это опись таблиц и представлений, а не SELECT всех товаров. getSchema и getSession дают связанные объекты; для проверки соединения мы сравниваем серверный CONNECTION_ID, а не номера PHP-объектов. VIEW — сохранённое определение SELECT; оно не содержит самостоятельную копию выбранных строк.

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

НаблюдениеРезультат
До CREATE TABLE объект существовал в базенет
После CREATE TABLEда
getName соответствует созданному именида
getSchema соответствует выбранной схемеда
getSession ведёт к тому же соединениюда
Исходная таблица является VIEWнет
Созданное представление является VIEWда
Своих объектов в getTables2
Всего строк в таблице3
Активные товары через VIEWLED, CABLE
Самостоятельный файл и архив для запуска
Узнать таблицу, её схему и представлениеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141Name('handles');
    $table = $schema->getTable($name);
    $before = $table->existsInDatabase();
    $qualified = x141Qualified($schema,$name);
    $session->sql('CREATE TABLE '.$qualified.' (id INT PRIMARY KEY, sku VARCHAR(32) NOT NULL, active TINYINT NOT NULL) ENGINE=InnoDB')->execute();
    $owned[] = ['type'=>'TABLE','name'=>$name];
    $table->insert('id','sku','active')->values([1,'LED',1],[2,'CABLE',1],[3,'OLD',0])->execute();
    $viewName = x141OwnView($session,$schema,$owned,'active_view','SELECT id,sku FROM '.$qualified.' WHERE active=1');
    $view = $schema->getTable($viewName);
    $listed = $schema->getTables();
    $ownListed = [];
    foreach ([$name,$viewName] as $ownName) {
        if (isset($listed[$ownName])) {$ownListed[$ownName] = get_debug_type($listed[$ownName]);}
    }
    $connection = $session->sql('SELECT CONNECTION_ID() AS connection_id')->execute()->fetchOne();
    $sameSession = $table->getSession();
    $viaTable = $sameSession->sql('SELECT CONNECTION_ID() AS connection_id')->execute()->fetchOne();
    $viewRows = $view->select('sku')->orderby('id ASC')->execute()->fetchAll();
    $rows = [['До CREATE TABLE объект существовал в базе',$before],['После CREATE TABLE',$table->existsInDatabase()],
        ['getName соответствует созданному имени',$table->getName() === $name],
        ['getSchema соответствует выбранной схеме',$table->getSchema()->getName() === $config['schema']],
        ['getSession ведёт к тому же соединению',$connection['connection_id'] === $viaTable['connection_id']],
        ['Исходная таблица является VIEW',$table->isView()],['Созданное представление является VIEW',$view->isView()],
        ['Своих объектов в getTables',count($ownListed)],['Всего строк в таблице',$table->count()],
        ['Активные товары через VIEW',implode(', ',array_column($viewRows,'sku'))]];
    $checks = ['handle_before_create'=>$before === false,'same_handle_after_create'=>$table->existsInDatabase(),
        'correct_name'=>$table->getName() === $name,'correct_schema'=>$table->getSchema()->getName() === $config['schema'],
        'same_connection'=>$connection['connection_id'] === $viaTable['connection_id'],
        'view_distinguished'=>!$table->isView() && $view->isView(),
        'listed_owned_objects'=>count($ownListed) === 2,'row_count'=>$table->count() === 3,
        'view_rows'=>array_column($viewRows,'sku') === ['LED','CABLE'] && $view->count() === 2];
    $observations = ['name'=>$name,'view_name'=>$viewName,'listed_own_objects'=>$ownListed,
        'name_property'=>$table->name,'get_name'=>$table->getName(),'connection_id'=>$connection['connection_id'],
        'table_session_id'=>$viaTable['connection_id'],'view_rows'=>$viewRows];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-table-handles', 'Узнать таблицу, её схему и представление', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
До CREATE TABLE объект существовал в базе — нет
После CREATE TABLE — да
getName соответствует созданному имени — да
getSchema соответствует выбранной схеме — да
getSession ведёт к тому же соединению — да
Исходная таблица является VIEW — нет
Созданное представление является VIEW — да
Своих объектов в getTables — 2
Всего строк в таблице — 3
Активные товары через VIEW — LED, CABLE

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

Проверка существования полезна для диагностики, но между ней и запросом объект могут изменить или удалить: она не заменяет обработку ошибки операции. getTables показывает доступное в выбранной схеме; отсутствие в описи нельзя трактовать как отсутствие во всём сервере. Здесь сохраняются только два собственных имени, чтобы чужие таблицы не попадали в учебный отчёт. isView не говорит, можно ли обновлять конкретное представление: применимость записи определяется определением VIEW и правами. count у таблицы относится ко всем её строкам, у VIEW — к выдаче сохранённого запроса. В модуле 8.0.30 возвращённый getSession объект удерживается в $sameSession до завершения: уничтожение временного объекта Session способно закрыть общее соединение. Не используйте краткий getSession()->sql(...) с последующим немедленным уничтожением временного Session и не закрывайте его, пока другие объекты ещё работают.

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

Измените WHERE active=1 в определении VIEW на active=0. Изменится ли исходная таблица?

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

Представление вернёт OLD и count 1; таблица останется с тремя строками. Нужно изменить ожидания VIEW, но не ожидание общего числа товаров.

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

Импорт прислал название с апострофом, у одного товара нет заметки, у второго заметка намеренно пустая. Нужно сохранить эти различия и не допустить повтор артикула. Перечислим колонки один раз, передадим две строки значений и отдельно прочитаем то, что действительно сохранилось.

Путь данных этой программы
  1. columns: sku → name → note
  2. Строка 1: O'NEAL → Лампа O'Neal → NULL
  3. Строка 2: CABLE → Кабель → пустая строка
  4. stock не указан → сервер берёт DEFAULT 0
  5. UNIQUE не допускает повтор; слишком короткий values не дополняется угадыванием

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

$insert = $table->insert(['sku','name','note']);
$insert->values(["O'NEAL","Лампа O'Neal",null]);
$receipt = $insert->execute();

Порядок values соответствует порядку insert, а не алфавиту имён. Ключи ассоциативного массива значений не стоит использовать как скрытое сопоставление: учебная запись явно позиционная. Можно добавлять строки последовательными values или несколькими массивами в одном values; короткая сигнатура Manual показывает один массив, а пример и PECL 8.0.30 поддерживают несколько. Неуказанный stock получает DEFAULT; явно переданный null не означает «возьми DEFAULT». Апостроф внутри данных не нужно превращать в фрагмент SQL.

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

НаблюдениеРезультат
Строк до execute0
Добавлено строк2
Первое названиеЛампа O'Neal
Пропущенный stock получил DEFAULT0
note первой строки — NULLда
note второй строки — пустая строкада
Повтор UNIQUE sku отклонёнда
Неполная строка values отклоненада
Сохранено после отказов2
Самостоятельный файл и архив для запуска
Сопоставить колонки со значениями и разобрать отказ вставкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141OwnTable($session,$schema,$owned,'insert_rows',
        '(id INT AUTO_INCREMENT PRIMARY KEY, sku VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(80) NOT NULL, stock INT NOT NULL DEFAULT 0, note TEXT NULL) ENGINE=InnoDB');
    $table = $schema->getTable($name);
    $pending = $table->insert(['sku','name','note'])
        ->values(["O'NEAL","Лампа O'Neal",null],['CABLE','Кабель','']);
    $before = $table->count();
    $receipt = $pending->execute();
    $duplicate = x141Failure(static fn()=>$table->insert('sku','name')->values(["O'NEAL",'Повтор артикула'])->execute());
    $badLength = x141Failure(static fn()=>$table->insert('sku','name','note')->values(['BROKEN'])->execute());
    $saved = $table->select('sku','name','stock','note')->orderby('id ASC')->execute()->fetchAll();
    $rows = [['Строк до execute',$before],['Добавлено строк',$receipt->getAffectedItemsCount()],
        ['Первое название',$saved[0]['name']],['Пропущенный stock получил DEFAULT',$saved[0]['stock']],
        ['note первой строки — NULL',$saved[0]['note'] === null],['note второй строки — пустая строка',$saved[1]['note'] === ''],
        ['Повтор UNIQUE sku отклонён',$duplicate !== null],['Неполная строка values отклонена',$badLength !== null],
        ['Сохранено после отказов',$table->count()]];
    $checks = ['builder_not_executed'=>$before === 0,'two_saved'=>$receipt->getAffectedItemsCount() === 2,
        'quoted_name'=>$saved[0]['sku'] === "O'NEAL" && $saved[0]['name'] === "Лампа O'Neal",
        'default_stock'=>$saved[0]['stock'] === 0 && $saved[1]['stock'] === 0,
        'null_vs_empty'=>$saved[0]['note'] === null && $saved[1]['note'] === '',
        'duplicate_refused'=>$duplicate !== null,'wrong_arity_refused'=>$badLength !== null,'no_bad_rows'=>$table->count() === 2];
    $observations = ['stored_rows'=>$saved,'duplicate_exception_chain'=>$duplicate,'wrong_row_length_exception_chain'=>$badLength,
        'receipt_type'=>get_debug_type($receipt)];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-insert-rows', 'Сопоставить колонки со значениями и разобрать отказ вставки', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Строк до execute — 0
Добавлено строк — 2
Первое название — Лампа O'Neal
Пропущенный stock получил DEFAULT — 0
note первой строки — NULL — да
note второй строки — пустая строка — да
Повтор UNIQUE sku отклонён — да
Неполная строка values отклонена — да
Сохранено после отказов — 2

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

DEFAULT 0 здесь означает известный нулевой остаток по правилу импорта. Если остаток неизвестен по смыслу, нужна другая модель, а не автоматическая подстановка нуля. UNIQUE проверяется сервером, и предварительный поиск не заменяет его при параллельных запросах. Отказы записаны отдельно от квитанции успеха. Пример проверяет две конкретные неудачные одиночные вставки; он не обещает частичный успех смешанного большого пакета или автоматический rollback произвольной цепочки действий.

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

Что произойдёт, если поменять местами name и sku только в списке insert, оставив values прежними?

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

Значения попадут в другие столбцы по позиции: это логическая ошибка, даже если SQL-типы позволят запись. Менять нужно и описание колонок, и соответствующий порядок данных; проверяйте прочитанную строку, а не только число вставок.

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

Экран показывает по два активных товара не дороже 1500 рублей. В таблице есть архивная позиция, дорогой экран и длинное описание. Не станем отправлять все шесть строк и описания в PHP, чтобы там отбирать и сортировать: сервер вернёт конкретную страницу из нужных трёх полей.

Путь данных этой программы
  1. WHERE исключает HIDDEN и SCREEN
  2. ORDER BY price_cents,id: CLIP → CABLE → LED → STRIP
  3. LIMIT 2 OFFSET 0 отдаёт CLIP/CABLE
  4. LIMIT 2 OFFSET 2 отдаёт LED/STRIP; равные цены различает id
  5. description не входит в SELECT и не попадает в массив PHP

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

$rows = $table->select('id','sku','price_cents')
    ->where('active = :active AND price_cents <= :maximum')
    ->bind(['active'=>1,'maximum'=>150000])
    ->orderby('price_cents ASC','id ASC')->limit(2)->offset(2)
    ->execute()->fetchAll();

select задаёт колонки ответа, where — условие участия строки, bind — значения именованных мест :active и :maximum. Ключи массива bind пишутся без двоеточия. orderby задаёт порядок; ASC означает возрастание. id завершает сравнение при одинаковой цене. limit задаёт размер страницы, offset — сколько подходящих строк пропустить. Для страницы 2 при размере 2 смещение равно (2 - 1) × 2 = 2. Пустой массив означает корректно выполненный запрос без строк на этой странице.

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

НаблюдениеРезультат
Страница 1: артикулыCLIP, CABLE
Страница 1: цены, коп.5000, 30000
Страница 2: артикулыLED, STRIP
Страница 2: цены, коп.120000, 120000
Следующая страница пустада
Столбцы экранаid, sku, price_cents
Всего строк в таблице6
Самостоятельный файл и архив для запуска
Получить две страницы каталога прямо из базыСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141OwnTable($session,$schema,$owned,'catalog_page','(id INT PRIMARY KEY, sku VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(80) NOT NULL, price_cents INT UNSIGNED NOT NULL, stock INT UNSIGNED NOT NULL DEFAULT 0, active TINYINT NOT NULL DEFAULT 1, description TEXT NULL) ENGINE=InnoDB');
    $table = $schema->getTable($name);
    $qualified = x141Qualified($schema,$name);

    $seedRows = [
        [1,'CABLE','Кабель',30000,10,1,'Описание кабеля'],
        [2,'LED','Светильник',120000,4,1,str_repeat('Подробное описание. ',100)],
        [3,'STRIP','Лента',120000,2,1,'Описание ленты'],
        [4,'HIDDEN','Архивный товар',25000,1,0,'Не публикуем'],
        [5,'SCREEN','Экран',500000,1,1,'Дорогой товар'],
        [6,'CLIP','Крепление',5000,20,1,'Описание крепления'],
    ];
    $seedInsert = $table->insert('id','sku','name','price_cents','stock','active','description');
    foreach ($seedRows as $seedRow) {
        $seedInsert->values($seedRow);
    }
    $seedInsert->execute();

    $page1 = $table->select('id','sku','price_cents')->where('active = :active AND price_cents <= :maximum')
        ->bind(['active'=>1,'maximum'=>150000])->orderby('price_cents ASC','id ASC')->limit(2)->offset(0)->execute()->fetchAll();
    $page2 = $table->select(['id','sku','price_cents'])->where('active = :active AND price_cents <= :maximum')
        ->bind(['active'=>1,'maximum'=>150000])->orderby(['price_cents ASC','id ASC'])->limit(2)->offset(2)->execute()->fetchAll();
    $end = $table->select('id','sku')->where('active = :active AND price_cents <= :maximum')
        ->bind(['active'=>1,'maximum'=>150000])->orderby('price_cents ASC','id ASC')->limit(2)->offset(4)->execute()->fetchAll();
    $rows = [['Страница 1: артикулы',implode(', ',array_column($page1,'sku'))],
        ['Страница 1: цены, коп.',implode(', ',array_column($page1,'price_cents'))],
        ['Страница 2: артикулы',implode(', ',array_column($page2,'sku'))],
        ['Страница 2: цены, коп.',implode(', ',array_column($page2,'price_cents'))],
        ['Следующая страница пуста',$end === []],['Столбцы экрана',implode(', ',array_keys($page1[0]))],
        ['Всего строк в таблице',$table->count()]];
    $checks = ['first_page'=>array_column($page1,'id') === [6,1],
        'tied_prices_ordered'=>array_column($page2,'id') === [2,3],
        'bounded'=>count($page1) === 2 && count($page2) === 2,
        'no_overlap'=>array_intersect(array_column($page1,'id'),array_column($page2,'id')) === [],
        'empty_end'=>$end === [],'projection'=>array_keys($page1[0]) === ['id','sku','price_cents'],
        'whole_table_unchanged'=>$table->count() === 6];
    $observations = ['page_1'=>$page1,'page_2'=>$page2,'page_3'=>$end,'page_size'=>2,'maximum_cents'=>150000];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-server-catalog-page', 'Получить две страницы каталога прямо из базы', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Страница 1: артикулы — CLIP, CABLE
Страница 1: цены, коп. — 5000, 30000
Страница 2: артикулы — LED, STRIP
Страница 2: цены, коп. — 120000, 120000
Следующая страница пуста — да
Столбцы экрана — id, sku, price_cents
Всего строк в таблице — 6

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

SELECT * не всегда является ошибкой сам по себе, но здесь он добавил бы ненужные name/stock/active/description, передачу и память PHP. Даже явный список колонок не спасает от загрузки сотен тысяч лишних строк без where/limit. Для больших данных нужен подходящий индекс и план запроса; маленький пример не измеряет их скорость. Большое offset может требовать пропустить много строк. Уникальный завершающий ключ делает порядок определённым для неизменяемого набора; изменения между запросами страниц всё ещё могут смещать позиции. Для длительной навигации рассматривайте курсор по ключам или согласованный снимок.

В лабораторном модуле PECL mysql_xdevapi 8.0.30 обнаружена особенность времени жизни данных TableInsert: при подготовке строки с динамически созданным описанием временный массив внутри values завершал процесс PHP до чтения каталога. Исходник расширения сохраняет значение массива без увеличения счётчика ссылок и читает его позже, при execute. Поэтому две программы каталога сначала сохраняют все строки в $seedRows, затем передают их через foreach в values и удерживают $seedRows до выполнения execute. Текст описания и его длина остаются прежними. Это ограничение конкретной реализации драйвера, не общий запрет PHP на временные массивы или Unicode; обычный try/catch не перехватывает такой аварийный выход процесса.

При постраничном чтении одинаковые значения поля сортировки дают непредсказуемую границу страниц. Реальная ситуация на Stack Overflow. Каталог сортируется по паре price_cents, id: уникальный id разрешает равенство цен. Это фиксирует порядок неизменного набора; изменения данных между запросами по-прежнему требуют отдельной стратегии пагинации.

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

Почему после удаления id из orderby нельзя обещать, какая из LED и STRIP окажется первой?

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

У них одинаковая цена. Без дополнительного уникального ключа сервер вправе вернуть их в любом взаимном порядке; это особенно заметно на границе страниц. Верните id и проверьте точные наборы строк, а не только их количество.

Прочитать первую строку и остаток результата без потерь

Для отчёта нужна первая карточка отдельно, а остальные — списком. Нельзя переносить поведение PDO на другой драйвер: в этом буферизованном RowResult fetchAll отдаёт весь буфер, даже после первого fetchOne. Покажем оба пути чтения на трёх товарах и отличим отсутствие строк от наличия описания колонок.

Путь данных этой программы
  1. SELECT задаёт aliases product_id, article, cost_cents
  2. Курсор перед LED → fetchOne возвращает LED и продвигает позицию
  3. fetchAll отдаёт весь буфер LED/CABLE/STRIP и не сдвигает fetchOne
  4. Последующие fetchOne возвращают CABLE, STRIP, затем null
  5. Отдельный SELECT id999 не нашёл строк, но описание двух колонок известно

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

$result = $table->select('id AS product_id','sku AS article')->orderby('id ASC')->execute();
$first = $result->fetchOne();
$buffer = $result->fetchAll();

RowResult — объект результата SELECT через Table API. fetchOne возвращает ассоциативный массив одной следующей строки; ключи соответствуют выбранным именам/aliases. В проверенном BUFFERED-результате 8.0.30 fetchAll отдаёт полный список буфера и не продвигает позицию fetchOne. Поэтому для чтения оставшихся строк используется while с fetchOne, а полный список можно сразу получить через fetchAll. Это два способа доступа к одному ответу, не повторное исполнение запроса. AS задаёт имя поля ответа; оно не переименовывает столбец в таблице. getColumnsCount и getColumnNames описывают поля результата.

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

НаблюдениеРезультат
Заголовки результатаproduct_id, article, cost_cents
Количество столбцов3
Первая строкаLED
fetchAll вернул весь буферLED, CABLE, STRIP
Повтор fetchAll дал тот же буферда
Оставшиеся строки через fetchOneCABLE, STRIP
fetchOne после конца — nullда
Пустой SELECT: строк0
Пустой SELECT: столбцов2
Пустой SELECT: имена колонокproduct_id, article
Самостоятельный файл и архив для запуска
Прочитать первую строку и остаток результата без потерьСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141OwnTable($session,$schema,$owned,'row_cursor','(id INT PRIMARY KEY, sku VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(80) NOT NULL, price_cents INT UNSIGNED NOT NULL, stock INT UNSIGNED NOT NULL DEFAULT 0, active TINYINT NOT NULL DEFAULT 1, description TEXT NULL) ENGINE=InnoDB');
    $table = $schema->getTable($name);
    $qualified = x141Qualified($schema,$name);

    $table->insert('id','sku','name','price_cents')->values([1,'LED','Светильник',120000],
        [2,'CABLE','Кабель',30000],[3,'STRIP','Лента',50000])->execute();
    $result = $table->select('id AS product_id','sku AS article','price_cents AS cost_cents')->orderby('id ASC')->execute();
    $columnCount = $result->getColumnsCount();
    $columnNames = $result->getColumnNames();
    $first = $result->fetchOne();
    $buffer = $result->fetchAll();
    $bufferAgain = $result->fetchAll();
    $remaining = [];
    while (($next = $result->fetchOne()) !== null) {$remaining[] = $next;}
    $afterEnd = $result->fetchOne();
    $empty = $table->select('id AS product_id','sku AS article')->where('id = :id')->bind(['id'=>999])->execute();
    $emptyNames = $empty->getColumnNames();
    $emptyColumnCount = $empty->getColumnsCount();
    $emptyRows = $empty->fetchAll();
    $rows = [['Заголовки результата',implode(', ',$columnNames)],['Количество столбцов',$columnCount],
        ['Первая строка',$first['article']],['fetchAll вернул весь буфер',implode(', ',array_column($buffer,'article'))],
        ['Повтор fetchAll дал тот же буфер',$bufferAgain === $buffer],
        ['Оставшиеся строки через fetchOne',implode(', ',array_column($remaining,'article'))],
        ['fetchOne после конца — null',$afterEnd === null],
        ['Пустой SELECT: строк',count($emptyRows)],['Пустой SELECT: столбцов',$emptyColumnCount],
        ['Пустой SELECT: имена колонок',implode(', ',$emptyNames)]];
    $checks = ['aliases'=>$columnNames === ['product_id','article','cost_cents'],
        'column_count'=>$columnCount === 3,'first_row'=>$first['article'] === 'LED',
        'buffer_all_rows'=>array_column($buffer,'article') === ['LED','CABLE','STRIP'] && $bufferAgain === $buffer,
        'remaining_rows'=>array_column($remaining,'article') === ['CABLE','STRIP'],
        'cursor_exhausted'=>$afterEnd === null,
        'empty_rows'=>$emptyRows === [],'metadata_without_rows'=>$emptyColumnCount === 2 && $emptyNames === ['product_id','article']];
    $observations = ['result_type'=>get_debug_type($result),'first'=>$first,'remaining_via_fetch_one'=>$remaining,
        'buffer'=>$buffer,'buffer_again'=>$bufferAgain,'after_end'=>$afterEnd,'empty_column_names'=>$emptyNames,'empty_rows'=>$emptyRows];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-row-result-cursor', 'Прочитать первую строку и остаток результата без потерь', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Заголовки результата — product_id, article, cost_cents
Количество столбцов — 3
Первая строка — LED
fetchAll вернул весь буфер — LED, CABLE, STRIP
Повтор fetchAll дал тот же буфер — да
Оставшиеся строки через fetchOne — CABLE, STRIP
fetchOne после конца — null — да
Пустой SELECT: строк — 0
Пустой SELECT: столбцов — 2
Пустой SELECT: имена колонок — product_id, article

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

В Manual для getColumnNames указано [] при пустом результате, но в проверяемом PECL 8.0.30 описание колонок может присутствовать и при нуле строк; эта программа сравнивает эти случаи отдельно. Не проверяйте наличие товаров по числу колонок. Метод называется getColumnsCount; до 8.0.14 использовалось getColumnCount. fetchAll большого неограниченного результата расходует память; для экрана ограничьте запрос до чтения. Если данные нужны дважды, сохраните небольшой список один раз либо осмысленно выполните новый запрос; уже прочитанный результат не считается автоматически перематываемым.

После предыдущего чтения результата разработчик неожиданно получает пустой массив. Реальная ситуация на Stack Overflow. Обсуждение относится к PDO и выявляет проблему состояния результата. Здесь показан собственный контракт X DevAPI: fetchOne продвигает позицию, а BUFFERED fetchAll в 8.0.30 повторно отдаёт весь буфер. Поведение одного драйвера не объявляется универсальным для всех.

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

Что произойдёт в этом буферизованном профиле, если сначала вызвать fetchAll, а потом первый fetchOne?

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

fetchAll содержит все три товара, а fetchOne всё ещё вернёт LED: чтение всего буфера не сдвинуло его позицию. Для простого шаблона сайта удобнее получить небольшой ограниченный список один раз и работать с ним, не смешивая способы чтения.

Передать значения в SQL и сравнить с TableSelect

Иногда SQL уже написан — например, его согласовали для отчёта. Можно выполнить его через существующий X Protocol Session без второго подключения. Отделим SQL-текст от артикула и минимального остатка, а затем получим тот же результат через TableSelect.

Путь данных этой программы
  1. SQL содержит два знака ?: первый sku, второй минимальный stock
  2. bind(LED) добавляет первое значение, bind(2) — второе
  3. Сервер сравнивает значения → одна строка LED, stock4
  4. Для TableSelect те же значения передаются именами sku/minimum
  5. Для IN двух значений нужны два места; выбор ORDER BY задаётся разрешённым SQL-шаблоном

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

$query = $session->sql('SELECT sku,stock FROM '.$qualified.' WHERE sku = ? AND stock >= ?');
$query->bind('LED')->bind(2);
$rows = $query->execute()->fetchAll();

В SQL X API placeholders позиционные: значения относятся к ? слева направо. В проверяемом PECL 8.0.30 один вызов SqlStatement::bind добавляет одно значение, поэтому используются два вызова; переносить variadic-запись из Node.js нельзя. TableSelect::bind принимает ассоциативный массив имён для :sku/:minimum. SQL-строка и значения — разные части операции. Апострофы и текст OR в значении sku остаются данными, не превращаются в условие для всех товаров.

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

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

НаблюдениеРезультат
SQL выбрал артикулLED
Его остаток4
TableSelect и SQL дали одинаковые строкида
Строка, похожая на SQL, дала совпадений0
Два значения INCABLE, LED
Сортировка из разрешённого спискаCLIP, CABLE
Всего строк после запросов6
Самостоятельный файл и архив для запуска
Передать значения в SQL и сравнить с TableSelectСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141OwnTable($session,$schema,$owned,'sql_parameters','(id INT PRIMARY KEY, sku VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(80) NOT NULL, price_cents INT UNSIGNED NOT NULL, stock INT UNSIGNED NOT NULL DEFAULT 0, active TINYINT NOT NULL DEFAULT 1, description TEXT NULL) ENGINE=InnoDB');
    $table = $schema->getTable($name);
    $qualified = x141Qualified($schema,$name);

    $seedRows = [
        [1,'CABLE','Кабель',30000,10,1,'Описание кабеля'],
        [2,'LED','Светильник',120000,4,1,str_repeat('Подробное описание. ',100)],
        [3,'STRIP','Лента',120000,2,1,'Описание ленты'],
        [4,'HIDDEN','Архивный товар',25000,1,0,'Не публикуем'],
        [5,'SCREEN','Экран',500000,1,1,'Дорогой товар'],
        [6,'CLIP','Крепление',5000,20,1,'Описание крепления'],
    ];
    $seedInsert = $table->insert('id','sku','name','price_cents','stock','active','description');
    foreach ($seedRows as $seedRow) {
        $seedInsert->values($seedRow);
    }
    $seedInsert->execute();

    $sql = 'SELECT id,sku,stock FROM '.$qualified.' WHERE sku = ? AND stock >= ? ORDER BY id';
    $prepared = $session->sql($sql)->bind('LED')->bind(2);
    $sqlRows = $prepared->execute()->fetchAll();
    $tableRows = $table->select('id','sku','stock')->where('sku = :sku AND stock >= :minimum')
        ->bind(['sku'=>'LED','minimum'=>2])->orderby('id ASC')->execute()->fetchAll();
    $hostile = "LED' OR 1=1 --";
    $literalRows = $session->sql($sql)->bind($hostile)->bind(2)->execute()->fetchAll();
    $twoSkus = $session->sql('SELECT sku FROM '.$qualified.' WHERE sku IN (?, ?) ORDER BY id')
        ->bind('CABLE')->bind('LED')->execute()->fetchAll();
    $allowedOrder = ['price'=>'price_cents ASC, id ASC','name'=>'name ASC, id ASC'];
    $requestedOrder = 'price';
    $sort = $allowedOrder[$requestedOrder] ?? $allowedOrder['price'];
    $ordered = $session->sql('SELECT sku FROM '.$qualified.' WHERE active = ? ORDER BY '.$sort.' LIMIT 2')
        ->bind(1)->execute()->fetchAll();
    $rows = [['SQL выбрал артикул',$sqlRows[0]['sku']],['Его остаток',$sqlRows[0]['stock']],
        ['TableSelect и SQL дали одинаковые строки',$sqlRows === $tableRows],
        ['Строка, похожая на SQL, дала совпадений',count($literalRows)],
        ['Два значения IN',implode(', ',array_column($twoSkus,'sku'))],
        ['Сортировка из разрешённого списка',implode(', ',array_column($ordered,'sku'))],
        ['Всего строк после запросов',$table->count()]];
    $checks = ['one_sql_match'=>count($sqlRows) === 1 && $sqlRows[0]['sku'] === 'LED' && $sqlRows[0]['stock'] === 4,
        'same_api_result'=>$sqlRows === $tableRows,'input_remained_value'=>$literalRows === [],
        'one_placeholder_each'=>array_column($twoSkus,'sku') === ['CABLE','LED'],
        'allowlisted_order'=>array_column($ordered,'sku') === ['CLIP','CABLE'],
        'no_table_change'=>$table->count() === 6];
    $observations = ['statement_type'=>get_debug_type($prepared),'sql_rows'=>$sqlRows,'table_rows'=>$tableRows,
        'literal_input'=>$hostile,'literal_input_rows'=>$literalRows,'in_rows'=>$twoSkus,'ordered_rows'=>$ordered];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-sql-parameters', 'Передать значения в SQL и сравнить с TableSelect', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
SQL выбрал артикул — LED
Его остаток — 4
TableSelect и SQL дали одинаковые строки — да
Строка, похожая на SQL, дала совпадений — 0
Два значения IN — CABLE, LED
Сортировка из разрешённого списка — CLIP, CABLE
Всего строк после запросов — 6

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

Один placeholder обозначает одно значение: IN (?) не означает автоматический список из строки "CABLE,LED". Для известной пары используются (?, ?) и два bind. Для переменной длины строят нужное число placeholders из проверенного размера списка, но не склеивают сами значения. Пустой список надо обработать явно. Имена таблиц, колонок, ASC/DESC и целые выражения ORDER BY не передаются как значения bind; для выбора сортировки служит фиксированный allowlist. x141Qualified проверяет собственные лабораторные имена. Здесь нет живого HTTP-теста: строка пользователя задана внутри воспроизводимого CLI-файла.

В лабораторном модуле PECL mysql_xdevapi 8.0.30 обнаружена особенность времени жизни данных TableInsert: при подготовке строки с динамически созданным описанием временный массив внутри values завершал процесс PHP до чтения каталога. Исходник расширения сохраняет значение массива без увеличения счётчика ссылок и читает его позже, при execute. Поэтому две программы каталога сначала сохраняют все строки в $seedRows, затем передают их через foreach в values и удерживают $seedRows до выполнения execute. Текст описания и его длина остаются прежними. Это ограничение конкретной реализации драйвера, не общий запрет PHP на временные массивы или Unicode; обычный try/catch не перехватывает такой аварийный выход процесса.

Разработчик пытается передать список значений в один параметр WHERE IN через X DevAPI другого языка. Реальная ситуация на Stack Overflow. В PHP-программе список из двух значений получает два знака ? и два последовательных bind. Синтаксис другого коннектора не переносится в PHP; технический контракт проверяется по исходникам расширения и отдельному запуску.

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

Поменяйте minimum с 2 на 5 в обоих вариантах запроса. Что получится?

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

У LED осталось 4, поэтому оба варианта вернут пустой массив. Это успешный запрос без совпадений; он отличается от ошибки SQL, нарушения числа параметров или потери соединения.

Различить строки SELECT, квитанцию записи и созданный ID

После создания товара нужно открыть его карточку, а не угадывать id по MAX(id) или счётчику PHP. Получим ID из квитанции именно этого INSERT, прочитаем товар по нему и отдельно посмотрим, чем SELECT без строк отличается от UPDATE без совпадений.

Путь данных этой программы
  1. INSERT создаёт LED; квитанция содержит affected 1 и фактический AUTO_INCREMENT id
  2. Этот id передаётся в новый SELECT через bind
  3. hasData до чтения true → fetchOne отдаёт Светильник
  4. После единственной строки hasData false, но fetchAll всё ещё отдаёт полный буфер из 1 строки
  5. SELECT NO-SUCH даёт []; UPDATE NO-SUCH даёт affected 0 — это разные операции

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

$receipt = $session->sql('INSERT INTO '.$qualified.' (sku,name) VALUES (?,?)')
    ->bind('LED')->bind('Светильник')->execute();
$id = $receipt->getLastInsertId();
$saved = $session->sql('SELECT id,sku,name FROM '.$qualified.' WHERE id = ?')
    ->bind($id)->execute()->fetchOne();

AUTO_INCREMENT назначает сервер. getLastInsertId читается у результата нужного INSERT, затем используется как значение запроса. Не предполагаем конкретное число или отсутствие пропусков: отображаем связь «полученный id → действительно прочитанный LED», а реальный id сохраняем в JSON. SqlStatementResult содержит квитанцию и/или строки. В PECL 8.0.30 hasData проверяет текущую возможность читать данные: после исчерпания строк оно меняется. Для ошибки сервера требуется обработка исключения; false/[] сами по себе не означают ошибку.

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

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

НаблюдениеРезультат
INSERT добавил строк1
INSERT имеет строку для чтениянет
Полученный ID позволил прочитать LEDда
SELECT имеет строку до fetchOneда
Прочитанное названиеСветильник
SELECT имеет строку после fetchOneнет
fetchAll после fetchOne: строк в буфере1
Пустой SELECT имеет строкунет
Пустой SELECT вернул массивда
UPDATE отсутствующего артикула изменил строк0
Самостоятельный файл и архив для запуска
Различить строки SELECT, квитанцию записи и созданный IDСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$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 = x141OwnTable($session,$schema,$owned,'sql_receipts',
        '(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, sku VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(80) NOT NULL) ENGINE=InnoDB');
    $table = $schema->getTable($name);
    $qualified = x141Qualified($schema,$name);
    $insert = $session->sql('INSERT INTO '.$qualified.' (sku,name) VALUES (?,?)')->bind('LED')->bind('Светильник')->execute();
    $id = $insert->getLastInsertId();
    $insertHasData = $insert->hasData();
    $read = $session->sql('SELECT id,sku,name FROM '.$qualified.' WHERE id = ?')->bind($id)->execute();
    $beforeFetch = $read->hasData();
    $saved = $read->fetchOne();
    $afterFetch = $read->hasData();
    $remaining = $read->fetchAll();
    $empty = $session->sql('SELECT id,sku FROM '.$qualified.' WHERE sku = ?')->bind('NO-SUCH')->execute();
    $emptyHasData = $empty->hasData();
    $emptyRows = $empty->fetchAll();
    $missingUpdate = $session->sql('UPDATE '.$qualified.' SET name = ? WHERE sku = ?')->bind('Новое имя')->bind('NO-SUCH')->execute();
    $rows = [['INSERT добавил строк',$insert->getAffectedItemsCount()],['INSERT имеет строку для чтения',$insertHasData],
        ['Полученный ID позволил прочитать LED',(string)$saved['id'] === (string)$id && $saved['sku'] === 'LED'],
        ['SELECT имеет строку до fetchOne',$beforeFetch],['Прочитанное название',$saved['name']],
        ['SELECT имеет строку после fetchOne',$afterFetch],['fetchAll после fetchOne: строк в буфере',count($remaining)],
        ['Пустой SELECT имеет строку',$emptyHasData],['Пустой SELECT вернул массив',is_array($emptyRows) && $emptyRows === []],
        ['UPDATE отсутствующего артикула изменил строк',$missingUpdate->getAffectedItemsCount()]];
    $checks = ['one_inserted'=>$insert->getAffectedItemsCount() === 1,
        'insert_no_rows'=>$insertHasData === false,'id_readback'=>(string)$saved['id'] === (string)$id && $saved['sku'] === 'LED',
        'cursor_data_changes'=>$beforeFetch === true && $afterFetch === false,
        'buffer_retained'=>count($remaining) === 1 && $remaining[0]['sku'] === 'LED',
        'empty_select'=>$emptyHasData === false && $emptyRows === [],
        'missing_update_zero'=>$missingUpdate->getAffectedItemsCount() === 0,
        'stored_name_unchanged'=>$table->select('name')->where('sku = :sku')->bind(['sku'=>'LED'])->execute()->fetchOne()['name'] === 'Светильник'];
    $observations = ['last_insert_id'=>$id,'last_insert_id_type'=>get_debug_type($id),'readback'=>$saved,
        'insert_type'=>get_debug_type($insert),'select_type'=>get_debug_type($read),'update_type'=>get_debug_type($missingUpdate),
        'insert_has_data'=>$insertHasData,'select_has_data_before'=>$beforeFetch,'select_has_data_after'=>$afterFetch,
        'empty_select_has_data'=>$emptyHasData,'empty_rows'=>$emptyRows,'missing_update_affected'=>$missingUpdate->getAffectedItemsCount()];
} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}
x141Report('mysql141-x-sql-result-receipts', 'Различить строки SELECT, квитанцию записи и созданный ID', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
INSERT добавил строк — 1
INSERT имеет строку для чтения — нет
Полученный ID позволил прочитать LED — да
SELECT имеет строку до fetchOne — да
Прочитанное название — Светильник
SELECT имеет строку после fetchOne — нет
fetchAll после fetchOne: строк в буфере — 1
Пустой SELECT имеет строку — нет
Пустой SELECT вернул массив — да
UPDATE отсутствующего артикула изменил строк — 0

Запустите файл в подготовленной отдельной лаборатории. result.html показывает фактические строки ответа, result.json — значения и наблюдения, metrics.json — проверки и удаление собственных объектов. Это CLI-пример с реальным MySQL, не проверка HTML-формы по HTTP.

Таблица сохранённого запуска · Данные результата.

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

PHP Manual показывает getLastInsertId(): String, но реализация 8.0.30 возвращает int, если значение меньше PHP_INT_MAX, и строку от PHP_INT_MAX включительно. В примере ID не приводится к int ради сравнения или вывода: сравниваются его строковые представления, фактический тип сохранён. Счётчик affected не заменяет количество строк SELECT и не объясняет любой нулевой UPDATE: ноль бывает при отсутствии совпадений или отсутствии изменения согласно режиму API. Для редактирования с версией есть следующий урок. hasData(false) не отличает INSERT от пустого SELECT; код уже знает выполненную операцию.

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

Почему нельзя заменить getLastInsertId запросом SELECT MAX(id)?

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

Параллельный клиент может успеть вставить свою строку; максимальный ID будет чужим. Нужна квитанция конкретной операции в своём соединении и отдельное чтение по её ID. Пропуски AUTO_INCREMENT допустимы и не являются доказательством потери строки.

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

APIДля чего
TableДоступ к строкам обычной таблицы через одно выбранное соединение.
TableInsertПлан вставки с явным соответствием колонок и строк значений.
TableSelectОписание нужного экрана: поля, фильтр, порядок и размер страницы.
RowResultСтроки и описание колонок, полученные TableSelect.
SqlStatementSQL-текст и его позиционные значения до исполнения.
SqlStatementResultОтвет обычного SQL: строки, счётчики и идентификатор вставки.
Session::sqlПодготовить обычный SQL в существующем X Protocol соединении.
Schema::getTableПолучить объект доступа к имени таблицы или VIEW в выбранной схеме.
Schema::getTablesПолучить опись доступных табличных объектов схемы.
Table::existsInDatabaseПроверить существование объекта перед диагностическим сообщением.
Table::getNameПолучить имя объекта для подписи и проверки маршрута данных.
Table::getSchemaВернуться к схеме, которой принадлежит выбранный объект.
Table::getSessionПолучить связанную с таблицей Session, сохранив её время жизни.
Table::isViewОтличить обычную таблицу от представления.
Table::countПосчитать строки всего выбранного объекта.
Table::insertЗадать столбцы новой строки до передачи её значений.
TableInsert::valuesДобавить строку значений в объявленном порядке колонок.
TableInsert::executeОтправить накопленную вставку и получить квитанцию.
Table::selectВыбрать только столбцы нужного экрана.
TableSelect::whereОтобрать подходящие строки до передачи в PHP.
TableSelect::bindСопоставить именам placeholders значения фильтра.
TableSelect::orderbyЗадать определённый порядок, включая равные цены.
TableSelect::limitОграничить число строк ответа до загрузки массива PHP.
TableSelect::offsetПропустить заданное число строк упорядоченного результата.
TableSelect::executeВыполнить собранное чтение и получить RowResult.
RowResult::fetchOneПрочитать следующую строку и проверить конец результата.
RowResult::fetchAllПолучить массив строк буферизованного ответа.
RowResult::getColumnsCountУзнать число полей выбранной проекции.
RowResult::getColumnNamesПолучить имена колонок ответа, включая aliases.
SqlStatement::bindДобавить очередное значение для позиционного знака вопроса.
SqlStatement::executeВыполнить SQL и получить фактический объект результата.
SqlStatementResult::hasDataПроверить, доступна ли очередная строка для курсорного чтения.
SqlStatementResult::fetchOneПолучить следующую строку обычного SQL SELECT.
SqlStatementResult::fetchAllПолучить массив строк SQL-ответа в буферизованном режиме.
SqlStatementResult::getAffectedItemsCountПрочитать квитанцию о числе затронутых строк SQL-операции.
SqlStatementResult::getLastInsertIdПолучить AUTO_INCREMENT идентификатор из квитанции нужного INSERT.

От чтения каталога к изменениям и связанным экранам

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

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

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

mysql_xdevapi\TableДоступ к строкам обычной таблицы через одно выбранное соединение.

PECL mysql_xdevapi >= 8.0.11; поведение программы проверяется на 8.0.30. · mysql_xdevapi

class mysql_xdevapi\Table implements mysql_xdevapi\SchemaObject

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

Путь конкретного вызова
  1. Schema + products → Table handle
  2. insert/select строят операции
  3. execute отправляет запрос → квитанция или строки

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

class mysql_xdevapi\Table implements mysql_xdevapi\SchemaObject
Параметр или часть вызоваЗначение
Получение$schema->getTable($name); прямой new не является способом создать таблицу.
СостояниеИмя и связь со Schema/Session; строки читаются отдельным запросом.

Возвращаемое значение. Объект Table предоставляет insert/select/update/delete и сведения о таблице/представлении.

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

Показать колонку артикула и цены без загрузки описания.

$table = $schema->getTable($name);
$rows = $table->select('sku','price_cents')->execute()->fetchAll();

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

Проверить, к какому реально существующему объекту ведёт выбранный handle.

$table = $schema->getTable($name);
$isReady = $table->existsInDatabase();
$isView = $table->isView();

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

Условия, ошибки и стоимость

Handle не означает CREATE TABLE и не является кэшем строк. Ошибки доступа/SQL обрабатываются у операций. Для крупного экрана задавайте выбор полей, фильтр и предел на сервере.

Ниже сохранён состав официального обзора класса. Это схема API, не исполняемый файл с определением встроенного класса. Закрытый конструктор, перечисленный в оглавлении, вызывается расширением; приложение получает объект от соответствующей операции.

class mysql_xdevapi\Table implements mysql_xdevapi\SchemaObject {
    public $name;
    public function count(): int;
    public function delete(): mysql_xdevapi\TableDelete;
    public function existsInDatabase(): bool;
    public function getName(): string;
    public function getSchema(): mysql_xdevapi\Schema;
    public function getSession(): mysql_xdevapi\Session;
    public function insert(mixed $columns, mixed ...$more_columns): mysql_xdevapi\TableInsert;
    public function isView(): bool;
    public function select(mixed $columns, mixed ...$more_columns): mysql_xdevapi\TableSelect;
    public function update(): mysql_xdevapi\TableUpdate;
}

Все узлы официального оглавления класса

Ссылки этого списка ведут к точным страницам PHP Manual. Наличие метода в обзоре не означает, что его подробный урок уже завершён. В этом пакете обучаются выбранные ниже операции; metadata объектов, warnings, блокировки TableSelect и несколько SQL-результатов требуют своих разборов.

Публичное name перечислено в Manual без подробного контракта. Для получения имени используйте документированный getName; присваивание свойству PHP не переименовывает таблицу в MySQL. Интерфейс SchemaObject означает связь со схемой, а не хранение всех строк внутри объекта.

Версии. PECL mysql_xdevapi >= 8.0.11; поведение программы проверяется на 8.0.30.

mysql_xdevapi\TableInsertПлан вставки с явным соответствием колонок и строк значений.

PECL mysql_xdevapi >= 8.0.11. · mysql_xdevapi

class mysql_xdevapi\TableInsert implements mysql_xdevapi\Executable

План вставки с явным соответствием колонок и строк значений.

Путь конкретного вызова
  1. insert(sku,name) задаёт две позиции
  2. values(LED,Светильник) заполняет позиции
  3. execute пишет → отдельный SELECT проверяет сохранение

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

class mysql_xdevapi\TableInsert implements mysql_xdevapi\Executable
Параметр или часть вызоваЗначение
Получение$table->insert(...колонки).
Накоплениеvalues добавляет одну или несколько строк; до execute запись не подтверждена.

Возвращаемое значение. Builder TableInsert; execute возвращает Result с квитанцией.

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

Увидеть границу: составленная операция ещё не записала товар.

$pending = $table->insert('id','sku','name','price_cents')->values([1,'LED','Светильник',120000]);
$before = $table->count();
$receipt = $pending->execute();

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

Импортировать пару строк с разными значениями NULL и пустой строки.

$table->insert('sku','name','note')
    ->values(["O'NEAL","Лампа O'Neal",null],['CABLE','Кабель',''])->execute();

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

Условия, ошибки и стоимость

Не путайте порядок колонок с порядком ключей JSON-документа. Ошибочная длина строки или уникальный артикул вызывают отказ; автоматическое исправление входных данных не обещается.

В лабораторном модуле PECL mysql_xdevapi 8.0.30 обнаружена особенность времени жизни данных TableInsert: при подготовке строки с динамически созданным описанием временный массив внутри values завершал процесс PHP до чтения каталога. Исходник расширения сохраняет значение массива без увеличения счётчика ссылок и читает его позже, при execute. Поэтому две программы каталога сначала сохраняют все строки в $seedRows, затем передают их через foreach в values и удерживают $seedRows до выполнения execute. Текст описания и его длина остаются прежними. Это ограничение конкретной реализации драйвера, не общий запрет PHP на временные массивы или Unicode; обычный try/catch не перехватывает такой аварийный выход процесса.

Ниже сохранён состав официального обзора класса. Это схема API, не исполняемый файл с определением встроенного класса. Закрытый конструктор, перечисленный в оглавлении, вызывается расширением; приложение получает объект от соответствующей операции.

class mysql_xdevapi\TableInsert implements mysql_xdevapi\Executable {
    public function execute(): mysql_xdevapi\Result;
    public function values(array $row_values): mysql_xdevapi\TableInsert;
}

Все узлы официального оглавления класса

Ссылки этого списка ведут к точным страницам PHP Manual. Наличие метода в обзоре не означает, что его подробный урок уже завершён. В этом пакете обучаются выбранные ниже операции; metadata объектов, warnings, блокировки TableSelect и несколько SQL-результатов требуют своих разборов.

Manual в короткой записи values показывает один массив, но его пример передаёт несколько. Matching source 8.0.30 принимает несколько массивов строк; оба способа — несколько values и несколько массивов в одном values — показаны в программах. Executable обозначает наличие execute, а не уже выполненную операцию.

Версии. PECL mysql_xdevapi >= 8.0.11.

mysql_xdevapi\TableSelectОписание нужного экрана: поля, фильтр, порядок и размер страницы.

PECL mysql_xdevapi >= 8.0.11. · mysql_xdevapi

class mysql_xdevapi\TableSelect implements mysql_xdevapi\Executable

Описание нужного экрана: поля, фильтр, порядок и размер страницы.

Путь конкретного вызова
  1. select(id,sku,price) выбирает форму ответа
  2. where отсеивает архив
  3. orderby+limit/offset задают страницу
  4. execute → RowResult

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

class mysql_xdevapi\TableSelect implements mysql_xdevapi\Executable
Параметр или часть вызоваЗначение
Получение$table->select(...поля).
Настройкиwhere/bind/orderby/limit/offset настраивают одно условие чтения.

Возвращаемое значение. Builder TableSelect; execute получает RowResult.

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

Подготовить ограниченную страницу каталога.

$page = $table->select('id','sku','price_cents')->where('active=:active')
    ->bind(['active'=>1])->orderby('price_cents ASC','id ASC')->limit(2)->execute();

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

Проверить экран «товар не найден» с известной формой полей.

$result = $table->select('id AS product_id','sku AS article')
    ->where('id=:id')->bind(['id'=>999])->execute();

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

Условия, ошибки и стоимость

Это API одной таблицы, не выдуманный конструктор JOIN. Для связанных таблиц используется Session::sql в следующей главе. Не выдавайте ограничение количества полученных строк за доказательство дешёвого плана сервера.

Ниже сохранён состав официального обзора класса. Это схема API, не исполняемый файл с определением встроенного класса. Закрытый конструктор, перечисленный в оглавлении, вызывается расширением; приложение получает объект от соответствующей операции.

class mysql_xdevapi\TableSelect implements mysql_xdevapi\Executable {
    public function bind(array $placeholder_values): mysql_xdevapi\TableSelect;
    public function execute(): mysql_xdevapi\RowResult;
    public function groupBy(mixed $sort_expr): mysql_xdevapi\TableSelect;
    public function having(string $sort_expr): mysql_xdevapi\TableSelect;
    public function limit(int $rows): mysql_xdevapi\TableSelect;
    public function lockExclusive(int $lock_waiting_option = ?): mysql_xdevapi\TableSelect;
    public function lockShared(int $lock_waiting_option = ?): mysql_xdevapi\TableSelect;
    public function offset(int $position): mysql_xdevapi\TableSelect;
    public function orderby(mixed $sort_expr, mixed ...$sort_exprs): mysql_xdevapi\TableSelect;
    public function where(string $where_expr): mysql_xdevapi\TableSelect;
}

Все узлы официального оглавления класса

Ссылки этого списка ведут к точным страницам PHP Manual. Наличие метода в обзоре не означает, что его подробный урок уже завершён. В этом пакете обучаются выбранные ниже операции; metadata объектов, warnings, блокировки TableSelect и несколько SQL-результатов требуют своих разборов.

Знак ? в значении по умолчанию lock_waiting_option оставлен как в официальном обзоре: это обозначение неизвестного здесь default, не PHP-файл для копирования. Группировка раскрывается в связанной главе изменений/отчётов; блокировки нельзя считать изученными лишь по этому списку. Получение результата происходит через execute, тогда как where/orderby/limit только настраивают запрос.

Версии. PECL mysql_xdevapi >= 8.0.11.

mysql_xdevapi\RowResultСтроки и описание колонок, полученные TableSelect.

PECL mysql_xdevapi >= 8.0.11; getColumnsCount переименован в 8.0.14. · mysql_xdevapi

class mysql_xdevapi\RowResult implements mysql_xdevapi\BaseResult, Traversable

Строки и описание колонок, полученные TableSelect.

Путь конкретного вызова
  1. SELECT отдаёт строки и имена полей
  2. fetchOne читает очередную строку
  3. BUFFERED fetchAll отдаёт весь массив, не сдвигая fetchOne

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

class mysql_xdevapi\RowResult implements mysql_xdevapi\BaseResult, Traversable
Параметр или часть вызоваЗначение
Получение$table->select(...)->execute().
ЧтениеfetchOne — следующая строка; fetchAll — массив результата; metadata относится к проекции.

Возвращаемое значение. RowResult хранит ответ; это не Table и не новая операция SELECT.

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

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

$first = $result->fetchOne();
$all = $result->fetchAll();

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

Построить подписи для выбранных aliases, даже если подходящих строк нет.

$columns = $result->getColumnNames();
$width = $result->getColumnsCount();

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

Условия, ошибки и стоимость

Семантику другой библиотеки, например PDO, нельзя переносить механически. Буфер большого запроса занимает память: ограничение страницы делается до execute. Метаданные не показывают количество строк.

Ниже сохранён состав официального обзора класса. Это схема API, не исполняемый файл с определением встроенного класса. Закрытый конструктор, перечисленный в оглавлении, вызывается расширением; приложение получает объект от соответствующей операции.

class mysql_xdevapi\RowResult implements mysql_xdevapi\BaseResult, Traversable {
    public function fetchAll(): array;
    public function fetchOne(): array;
    public function getColumnsCount(): int;
    public function getColumnNames(): array;
    public function getColumns(): array;
    public function getWarnings(): array;
    public function getWarningsCount(): int;
}

Все узлы официального оглавления класса

Ссылки этого списка ведут к точным страницам PHP Manual. Наличие метода в обзоре не означает, что его подробный урок уже завершён. В этом пакете обучаются выбранные ниже операции; metadata объектов, warnings, блокировки TableSelect и несколько SQL-результатов требуют своих разборов.

Traversable разрешает обход результата; для повторного использования небольшого экрана проще сохранить ограниченный массив. В этом уроке разобраны два фактических пути чтения: fetchOne продвигает позицию, fetchAll в BUFFERED 8.0.30 читает весь буфер. Краткий тип array у fetchOne не показывает документированный null при конце строк.

Версии. PECL mysql_xdevapi >= 8.0.11; getColumnsCount переименован в 8.0.14.

mysql_xdevapi\SqlStatementSQL-текст и его позиционные значения до исполнения.

PECL mysql_xdevapi >= 8.0.11. · mysql_xdevapi

class mysql_xdevapi\SqlStatement

SQL-текст и его позиционные значения до исполнения.

Путь конкретного вызова
  1. sql задаёт SQL-шаблон
  2. bind добавляет первое, затем второе значение
  3. execute получает квитанцию/строки

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

class mysql_xdevapi\SqlStatement
Параметр или часть вызоваЗначение
Получение$session->sql($query).
ПараметрыВ тексте — ?, каждое значение добавляется отдельным bind в порядке появления.

Возвращаемое значение. Объект операции; actual execute на данном профиле возвращает SqlStatementResult.

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

Выполнить согласованный SQL для выбранного артикула.

$statement = $session->sql('SELECT sku FROM '.$qualified.' WHERE sku=? AND stock>=?');
$statement->bind('LED')->bind(2);
$rows = $statement->execute()->fetchAll();

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

Использовать тот же механизм для записи с квитанцией.

$insert = $session->sql('INSERT INTO '.$qualified.' (sku,name) VALUES (?,?)');
$receipt = $insert->bind('LED')->bind('Светильник')->execute();

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

Условия, ошибки и стоимость

В коротком PHP Manual bind указан string, execute — Result; описание не заполнено. Здесь отмечается фактический интерфейс 8.0.30, а не произвольно дописывается вариативный API из Node.js. Значения bind не задают SQL-имена и ключевые слова.

Ниже сохранён состав официального обзора класса. Это схема API, не исполняемый файл с определением встроенного класса. Закрытый конструктор, перечисленный в оглавлении, вызывается расширением; приложение получает объект от соответствующей операции.

class mysql_xdevapi\SqlStatement {
    const int EXECUTE_ASYNC = 1;
    const int BUFFERED = 2;
    public $statement;
    public function bind(string $param): mysql_xdevapi\SqlStatement;
    public function execute(): mysql_xdevapi\Result;
    public function getNextResult(): mysql_xdevapi\Result;
    public function getResult(): mysql_xdevapi\Result;
    public function hasMoreResults(): bool;
}

Все узлы официального оглавления класса

Ссылки этого списка ведут к точным страницам PHP Manual. Наличие метода в обзоре не означает, что его подробный урок уже завершён. В этом пакете обучаются выбранные ниже операции; metadata объектов, warnings, блокировки TableSelect и несколько SQL-результатов требуют своих разборов.

Свойство statement перечислено в Manual без подробной семантики; оно не заменяет вызовы sql/bind/execute. EXECUTE_ASYNC=1 относится к асинхронному исполнению, BUFFERED=2 — к буферизации результата. Исходник 8.0.30 допускает необязательный флаг execute с BUFFERED по умолчанию, хотя обзор не показывает аргумент. Здесь все программы синхронные и используют default. Методы getResult/getNextResult/hasMoreResults перечислены полностью, но их вызовы не имитируются без отдельного разбора жизненного цикла.

Версии. PECL mysql_xdevapi >= 8.0.11.

mysql_xdevapi\SqlStatementResultОтвет обычного SQL: строки, счётчики и идентификатор вставки.

PECL mysql_xdevapi >= 8.0.11. · mysql_xdevapi

class mysql_xdevapi\SqlStatementResult implements mysql_xdevapi\BaseResult, Traversable

Ответ обычного SQL: строки, счётчики и идентификатор вставки.

Путь конкретного вызова
  1. INSERT → affected 1 + id
  2. SELECT по id → row LED
  3. hasData зависит от текущей позиции fetchOne
  4. UPDATE без совпадений → affected 0

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

class mysql_xdevapi\SqlStatementResult implements mysql_xdevapi\BaseResult, Traversable
Параметр или часть вызоваЗначение
Получение$session->sql(...)->execute() на проверяемом профиле.
НазначениеПроверять результат именно выполненной операции, не угадывать его по одному числу.

Возвращаемое значение. Для SELECT доступны строки; для INSERT/UPDATE — квитанция. Тип объекта сам по себе не означает непустую выборку.

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

Получить адрес созданной карточки и квитанцию её записи.

$id = $insert->getLastInsertId();
$added = $insert->getAffectedItemsCount();

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

Прочитать карточку и различить текущую позицию и сохранённый буфер.

$available = $read->hasData();
$row = $read->fetchOne();
$buffer = $read->fetchAll();

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

Условия, ошибки и стоимость

Нулевой affected не объясняет причину сам по себе. hasData не является постоянным признаком SQL SELECT. Весь набор metadata/warnings/multi-result перечислен в synopsis, но не объявлен раскрытым только по наличию класса.

Ниже сохранён состав официального обзора класса. Это схема API, не исполняемый файл с определением встроенного класса. Закрытый конструктор, перечисленный в оглавлении, вызывается расширением; приложение получает объект от соответствующей операции.

class mysql_xdevapi\SqlStatementResult implements mysql_xdevapi\BaseResult, Traversable {
    public function fetchAll(): array;
    public function fetchOne(): array;
    public function getAffectedItemsCount(): int;
    public function getColumnsCount(): int;
    public function getColumnNames(): array;
    public function getColumns(): array;
    public function getGeneratedIds(): array;
    public function getLastInsertId(): string;
    public function getWarnings(): array;
    public function getWarningsCount(): int;
    public function hasData(): bool;
    public function nextResult(): mysql_xdevapi\Result;
}

Все узлы официального оглавления класса

Ссылки этого списка ведут к точным страницам PHP Manual. Наличие метода в обзоре не означает, что его подробный урок уже завершён. В этом пакете обучаются выбранные ниже операции; metadata объектов, warnings, блокировки TableSelect и несколько SQL-результатов требуют своих разборов.

В официальном synopsis есть опечатка getWarningCounts(), тогда как оглавление и PECL 8.0.30 используют getWarningsCount(); выше показано действительное имя. Краткие типы в обзоре требуют уточнений на карточках: fetchOne может дать null, getLastInsertId и большой affected count зависят от диапазона PHP integer. nextResult здесь сохранён в обзоре, но не объявляется изученным или выполненным.

Версии. PECL mysql_xdevapi >= 8.0.11.

mysql_xdevapi\Session::sqlПодготовить обычный SQL в существующем X Protocol соединении.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Session::sql(string $query): mysql_xdevapi\SqlStatement

Подготовить обычный SQL в существующем X Protocol соединении.

Путь конкретного вызова
  1. SQL SELECT sku,stock WHERE sku=?
  2. sql возвращает объект операции
  3. bind LED → execute → строка каталога

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

Session::sql(string $query): mysql_xdevapi\SqlStatement
Параметр или часть вызоваЗначение
querySQL-строка; значения обозначаются native placeholders ?. Сам вызов ещё не исполняет SQL.

Возвращаемое значение. SqlStatement: затем bind для значений и execute для отправки.

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

Прочитать товар из уже написанного SQL.

$query = $session->sql('SELECT sku,stock FROM '.$qualified.' WHERE sku=?');
$rows = $query->bind('LED')->execute()->fetchAll();

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

Явно подготовить собственную таблицу до прикладных запросов; в архиве это делает x141OwnTable.

$session->sql('CREATE TABLE '.$qualified.' (id INT PRIMARY KEY, sku VARCHAR(32)) ENGINE=InnoDB')->execute();

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

Условия, ошибки и стоимость

sql не является экранированием произвольного текста. Передавайте данные через bind; имена объектов — из доверенного шаблона/allowlist. DDL может неявно фиксировать транзакцию, поэтому подготовка не смешивается с бизнес-изменениями. SQL синтаксис и права проверяются при исполнении; не превращайте отказ в «ничего не найдено».

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

mysql_xdevapi\Schema::getTableПолучить объект доступа к имени таблицы или VIEW в выбранной схеме.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Schema::getTable(string $name): mysql_xdevapi\Table

Получить объект доступа к имени таблицы или VIEW в выбранной схеме.

Путь конкретного вызова
  1. Schema хранит имя учебной базы
  2. getTable(name) возвращает handle
  3. existsInDatabase проверяет, есть ли хранилище

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

Schema::getTable(string $name): mysql_xdevapi\Table
Параметр или часть вызоваЗначение
nameИмя объекта внутри этой Schema; не полный SQL-запрос и не набор строк.

Возвращаемое значение. Table handle, который может быть получен ещё до существования самой таблицы.

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

Отличить адрес будущей таблицы от уже созданного объекта.

$table = $schema->getTable($name);
$exists = $table->existsInDatabase();

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

Читать VIEW через тот же Table интерфейс.

$active = $schema->getTable($viewName);
$rows = $active->select('sku')->execute()->fetchAll();

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

Условия, ошибки и стоимость

getTable не выполняет CREATE и не гарантирует доступность в момент последующего запроса. Для имени из пользовательского ввода нужен разрешённый набор объектов, даже если имя передаётся API, а не склеивается с SQL. Handle привязан к конкретной Schema/Session; одинаковое имя в другой базе — другой объект.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

mysql_xdevapi\Schema::getTablesПолучить опись доступных табличных объектов схемы.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Schema::getTables(): array

Получить опись доступных табличных объектов схемы.

Путь конкретного вызова
  1. getTables запрашивает опись схемы
  2. имя таблицы → Table
  3. выбираем два собственных имени, не читаем чужие строки

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

Schema::getTables(): array
Параметр или часть вызоваЗначение
ПараметрыОтсутствуют: используется текущая Schema.

Возвращаемое значение. Массив: ключ — имя, значение — Table. В проверенном профиле в опись входят также VIEW.

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

Проверить, что подготовленная таблица появилась в описи.

$tables = $schema->getTables();
$found = isset($tables[$name]);

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

Различить таблицу и представление в диагностическом списке.

$tables = $schema->getTables();
$viewHandle = $tables[$viewName];
$isView = $viewHandle->isView();

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

Условия, ошибки и стоимость

Это не массив всех строк таблиц. Не считайте length описи размером каталога, и не вызывайте автоматически SELECT * для каждого объекта. Результат относится к этой схеме и правам соединения. На странице Manual стоит пометка неполной документации, но состав результата и пример описаны; здесь они уточнены реальным сценарием.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

mysql_xdevapi\Table::existsInDatabaseПроверить существование объекта перед диагностическим сообщением.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::existsInDatabase(): bool

Проверить существование объекта перед диагностическим сообщением.

Путь конкретного вызова
  1. Table handle создан до DDL
  2. проверка false
  3. после CREATE тот же handle проверяется true

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

Table::existsInDatabase(): bool
Параметр или часть вызоваЗначение
ПараметрыНет; имя и схема уже находятся в Table.

Возвращаемое значение. true, если объект существует; false при его отсутствии. Отказ связи или прав не нужно объявлять отсутствием автоматически.

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

Показать, почему создание PHP-объекта ещё не означает существование таблицы.

$existsBefore = $table->existsInDatabase();

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

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

$ready = $table->existsInDatabase();
$count = $table->count();

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

Условия, ошибки и стоимость

Между проверкой и действием возможна смена состояния; обработка ошибок самого запроса всё равно нужна. В этой версии не уничтожайте временные Session-объекты до завершения работы: закрытое соединение способно исказить вспомогательную диагностику. Наличие таблицы и право читать нужные поля — разные условия.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::getNameПолучить имя объекта для подписи и проверки маршрута данных.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::getName(): string

Получить имя объекта для подписи и проверки маршрута данных.

Путь конкретного вызова
  1. Создано случайное собственное имя
  2. getTable сохраняет адрес
  3. getName возвращает этот адрес для отчёта

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

Table::getName(): string
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Строка имени табличного объекта, к которому привязан handle.

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

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

$sameName = $table->getName() === $name;

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

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

$viewLabel = $view->getName();

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

Условия, ошибки и стоимость

Имя не является SELECT и не доказывает существование объекта. Если нужно вывести его в HTML, экранируйте как текст; если сформировать SQL-идентификатор, используйте отдельное правило допустимых имён/цитирования. Присваивание публичному name не выполняет RENAME TABLE; используйте метод чтения по назначению.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::getSchemaВернуться к схеме, которой принадлежит выбранный объект.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::getSchema(): mysql_xdevapi\Schema

Вернуться к схеме, которой принадлежит выбранный объект.

Путь конкретного вызова
  1. Table products принадлежит Schema
  2. getSchema возвращает объект владельца
  3. getName сообщает имя выбранной базы

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

Table::getSchema(): mysql_xdevapi\Schema
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Объект Schema, связанный с таблицей.

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

Обнаружить чтение одноимённой таблицы из неверной учебной базы.

$owner = $table->getSchema();
$expected = $owner->getName() === $config['schema'];

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

Найти табличные объекты рядом с выбранным представлением.

$owner = $view->getSchema();
$objects = $owner->getTables();

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

Условия, ошибки и стоимость

Метод не переносит таблицу в другую схему и не выдаёт права на другие базы. Возвращается объект, а не массив настроек. Используйте его getName/getTables по назначению; не извлекайте параметры подключения из внутреннего устройства PHP-объекта.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::getSessionПолучить связанную с таблицей Session, сохранив её время жизни.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::getSession(): mysql_xdevapi\Session

Получить связанную с таблицей Session, сохранив её время жизни.

Путь конкретного вызова
  1. Table принадлежит соединению
  2. getSession создаёт связанную PHP-обёртку
  3. сохраняем её в переменную до конца операций
  4. оба пути дают один CONNECTION_ID

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

Table::getSession(): mysql_xdevapi\Session
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Объект Session над тем же native соединением; это не обещание идентичности PHP-объектов через ===.

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

Проверить фактическое соединение, обслуживающее таблицу.

$sameSession = $table->getSession();
$identity = $sameSession->sql('SELECT CONNECTION_ID() AS connection_id')->execute()->fetchOne();

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

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

$sameSession = $table->getSession();
$serverVersion = $sameSession->getServerVersion();

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

Условия, ошибки и стоимость

В 8.0.30 уничтожение возвращённой обёртки Session может закрыть общее соединение. Поэтому удерживайте переменную до конца работы и cleanup, не делайте get_class($table->getSession()) или короткую цепочку с временной обёрткой. Не вызывайте close/unset раньше завершения всех зависимых объектов. Этот нюанс подтверждён отдельной диагностикой; не следует переносить его на все драйверы.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::isViewОтличить обычную таблицу от представления.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::isView(): bool

Отличить обычную таблицу от представления.

Путь конкретного вызова
  1. products хранит три строки
  2. active_view хранит определение SELECT active=1
  3. isView различает типы; чтение VIEW даёт два товара

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

Table::isView(): bool
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. true для VIEW, false для обычного табличного объекта.

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

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

$baseIsView = $table->isView();

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

Объяснить пользователю происхождение списка активных товаров.

$projectionIsView = $view->isView();
$rows = $view->select('sku')->execute()->fetchAll();

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

Условия, ошибки и стоимость

isView не является проверкой обновляемости, прав или скорости VIEW. Представление не всегда означает материализованную копию; в этом примере это сохранённый SELECT над исходной таблицей. Не выбирайте команду удаления случайного объекта по одному флагу: список владения лабораторией хранит явный тип TABLE/VIEW.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::countПосчитать строки всего выбранного объекта.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::count(): int

Посчитать строки всего выбранного объекта.

Путь конкретного вызова
  1. Исходная таблица LED/CABLE/OLD → count3
  2. VIEW active=1 → count2
  3. число не равно размеру страницы из limit2

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

Table::count(): int
Параметр или часть вызоваЗначение
ПараметрыНет; where/limit к этому вызову не добавляются.

Возвращаемое значение. Количество строк в таблице либо выдаче VIEW.

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

Увидеть границу между подготовленной и выполненной вставкой.

$before = $table->count();
$receipt = $pending->execute();
$after = $table->count();

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

Сравнить все товары с результатом явно заданного фильтра VIEW.

$allProducts = $table->count();
$activeProducts = $view->count();

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

Условия, ошибки и стоимость

Для числа подходящих определённому фильтру строк используйте SELECT COUNT с тем же условием, а не count() у исходной Table после чужого where. Подсчёт большого набора может иметь стоимость; это не бесплатная метка размера. count не заменяет квитанцию конкретной операции и не показывает, кто добавил строки параллельно.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::insertЗадать столбцы новой строки до передачи её значений.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::insert(mixed $columns, mixed ...$more_columns): mysql_xdevapi\TableInsert

Задать столбцы новой строки до передачи её значений.

Путь конкретного вызова
  1. insert(sku,name,note) фиксирует три позиции
  2. values передаёт три значения
  3. отсутствующий stock получает DEFAULT

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

Table::insert(mixed $columns, mixed ...$more_columns): mysql_xdevapi\TableInsert
Параметр или часть вызоваЗначение
columnsИмя строкой либо массив из одного или нескольких имён колонок.
more_columnsДополнительные имена в следующих аргументах.

Возвращаемое значение. TableInsert; values задаёт строки, execute выполняет вставку.

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

Явно сопоставить поля карточки с данными.

$insert = $table->insert('id','sku','name','price_cents','stock');
$insert->values([1,'LED','Светильник',120000,4]);

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

Пропустить AUTO_INCREMENT id и stock с DEFAULT, сохранив пустую заметку.

$insert = $table->insert(['sku','name','note']);
$insert->values(['CABLE','Кабель','']);

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

Условия, ошибки и стоимость

Метод возвращает план, не подтверждение записи. Имена колонок задаются приложением; данные не нужно склеивать с SQL. Если колонка пропущена, сервер использует её правила DEFAULT/NULL; если обязательного значения нет и default не подходит, операция может быть отклонена. Не меняйте порядок колонок отдельно от values.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableInsert::valuesДобавить строку значений в объявленном порядке колонок.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableInsert::values(array $row_values): mysql_xdevapi\TableInsert

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

Путь конкретного вызова
  1. columns sku,name,note
  2. values [O'NEAL,Лампа O'Neal,NULL]
  3. второй values [CABLE,Кабель,пусто]
  4. execute пишет обе строки

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

TableInsert::values(array $row_values): mysql_xdevapi\TableInsert
Параметр или часть вызоваЗначение
row_valuesПозиционный массив значений одной строки; в 8.0.30 также принимаются несколько массивов строк за вызов, как в примере PHP Manual.

Возвращаемое значение. Тот же builder TableInsert для продолжения цепочки.

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

Накопить строки по одной для небольшого импорта.

$insert->values([1,'LED','Светильник',120000,4])
    ->values([2,'CABLE','Кабель',30000,10]);

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

Записать известную пару строк без самостоятельного SQL-экранирования значений.

$table->insert('sku','name','note')->values(["O'NEAL","Лампа O'Neal",null],['CABLE','Кабель',''])->execute();

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

Условия, ошибки и стоимость

Число значений должно соответствовать заданным колонкам. NULL, пустая строка и пропущенная колонка различаются. Нельзя передать один вложенный массив и считать его универсальным пакетом строк без понимания сигнатуры. Большой импорт делите на осмысленные порции и отдельно определяйте транзакционную политику, а не копируйте двухстрочный пример без границ.

В лабораторном модуле PECL mysql_xdevapi 8.0.30 обнаружена особенность времени жизни данных TableInsert: при подготовке строки с динамически созданным описанием временный массив внутри values завершал процесс PHP до чтения каталога. Исходник расширения сохраняет значение массива без увеличения счётчика ссылок и читает его позже, при execute. Поэтому две программы каталога сначала сохраняют все строки в $seedRows, затем передают их через foreach в values и удерживают $seedRows до выполнения execute. Текст описания и его длина остаются прежними. Это ограничение конкретной реализации драйвера, не общий запрет PHP на временные массивы или Unicode; обычный try/catch не перехватывает такой аварийный выход процесса.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableInsert. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableInsert::executeОтправить накопленную вставку и получить квитанцию.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableInsert::execute(): mysql_xdevapi\Result

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

Путь конкретного вызова
  1. До execute count0
  2. execute отправляет две строки
  3. Result affected2
  4. новый SELECT показывает сохранённые данные

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

TableInsert::execute(): mysql_xdevapi\Result
Параметр или часть вызоваЗначение
ПараметрыНет; колонки и строки уже заданы insert/values.

Возвращаемое значение. Result: квитанция записи, а не массив вставленных товаров.

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

Показать пользователю, сколько строк обработала вставка.

$receipt = $pending->execute();
$added = $receipt->getAffectedItemsCount();

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

Отдельно подтвердить содержимое сохранённой карточки, не показывая входной массив как результат БД.

$receipt = $table->insert('sku','name')->values(['CABLE','Кабель'])->execute();
$saved = $table->select('sku','name')->where('sku=:sku')->bind(['sku'=>'CABLE'])->execute()->fetchOne();

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

Условия, ошибки и стоимость

Повтор execute не является безопасным повтором импорта: он может повторно выполнить запись или встретить UNIQUE. Сбой сети после отправки требует отдельной политики повторов. Ошибка ограничений/прав выдаётся как отказ, а не успешный Result с выдуманным нулём. Для generated document IDs есть другой API; AUTO_INCREMENT обычного SQL разобран в SqlStatementResult.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableInsert. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\Table::selectВыбрать только столбцы нужного экрана.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

Table::select(mixed $columns, mixed ...$more_columns): mysql_xdevapi\TableSelect

Выбрать только столбцы нужного экрана.

Путь конкретного вызова
  1. Таблица содержит длинный description
  2. select(id,sku,price_cents) задаёт три поля ответа
  3. WHERE/LIMIT ограничивают строки отдельно

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

Table::select(mixed $columns, mixed ...$more_columns): mysql_xdevapi\TableSelect
Параметр или часть вызоваЗначение
columnsОдна строка выражения/имени либо массив из одного или нескольких выражений.
more_columnsДополнительные выражения отдельных аргументов.

Возвращаемое значение. TableSelect; execute возвращает RowResult.

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

Не передавать длинное описание в списке товаров.

$select = $table->select('id','sku','price_cents');

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

Задать ясные имена колонок в отчёте через AS.

$select = $table->select(['id AS product_id','sku AS article']);

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

Условия, ошибки и стоимость

Один выбор полей ещё не ограничивает число строк. SELECT * уместен лишь когда действительно нужны все колонки и известна форма данных; здесь он создаёт лишнюю передачу и связывает экран с будущими изменениями схемы. Выражения и имена берутся из кода/allowlist; bind служит для значений, а не для выбора колонки.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: Table. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableSelect::whereОтобрать подходящие строки до передачи в PHP.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableSelect::where(string $where_expr): mysql_xdevapi\TableSelect

Отобрать подходящие строки до передачи в PHP.

Путь конкретного вызова
  1. HIDDEN неактивен → исключён
  2. SCREEN дороже maximum → исключён
  3. оставшиеся строки сортируются и ограничиваются на сервере

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

TableSelect::where(string $where_expr): mysql_xdevapi\TableSelect
Параметр или часть вызоваЗначение
where_exprСтрока условия, например active=:active AND price_cents<=:maximum; значения передаются bind.

Возвращаемое значение. TableSelect для дальнейшей настройки и execute.

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

Показать только доступный ценовой диапазон каталога.

$select->where('active=:active AND price_cents<=:maximum')->bind(['active'=>1,'maximum'=>150000]);

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

Искать одну конкретную карточку и корректно обработать её отсутствие.

$select->where('id=:id')->bind(['id'=>999]);

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

Условия, ошибки и стоимость

Условие проверяется для строк; агрегатные условия относятся к having после groupBy. Пустой ответ не равен синтаксической ошибке. Не склеивайте введённый текст с условием и не фильтруйте уже загруженный полный каталог вместо запроса. Проверьте бизнес-смысл отсутствующего фильтра: без него TableSelect читает всё, что допускают остальные условия.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableSelect. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableSelect::bindСопоставить именам placeholders значения фильтра.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableSelect::bind(array $placeholder_values): mysql_xdevapi\TableSelect

Сопоставить именам placeholders значения фильтра.

Путь конкретного вызова
  1. :sku обозначает место в условии
  2. [sku=>LED] передаёт значение
  3. execute сравнивает sku со строкой LED

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

TableSelect::bind(array $placeholder_values): mysql_xdevapi\TableSelect
Параметр или часть вызоваЗначение
placeholder_valuesАссоциативный массив: ключ без двоеточия, значение — данные для соответствующего :name.

Возвращаемое значение. TableSelect; bind не исполняет запрос.

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

Передать артикул и минимальный остаток независимо от текста выражения.

$select->where('sku=:sku AND stock>=:minimum')->bind(['sku'=>'LED','minimum'=>2]);

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

Менять ценовой фильтр без конкатенации SQL.

$select->where('active=:active AND price_cents<=:maximum')->bind(['active'=>1,'maximum'=>150000]);

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

Условия, ошибки и стоимость

Этот метод отличается от SqlStatement::bind: там позиционные ? и одиночные значения. Placeholder должен присутствовать в сформированном выражении; имена не включают знак :. Bind не заменяет проверку бизнес-диапазона и не подставляет имя столбца/оператор. Несогласованные имена или отсутствующие значения требуют исправления запроса, а не молчаливого чтения всех строк.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableSelect. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableSelect::orderbyЗадать определённый порядок, включая равные цены.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableSelect::orderby(mixed $sort_expr, mixed ...$sort_exprs): mysql_xdevapi\TableSelect

Задать определённый порядок, включая равные цены.

Путь конкретного вызова
  1. CLIP5000 → CABLE30000
  2. LED120000 и STRIP120000 равны по цене
  3. id2 < id3 завершает сравнение
  4. граница страницы становится определённой

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

TableSelect::orderby(mixed $sort_expr, mixed ...$sort_exprs): mysql_xdevapi\TableSelect
Параметр или часть вызоваЗначение
sort_exprСтрока выражения порядка либо массив выражений.
sort_exprsДополнительные выражения; ASC/DESC задают направление.

Возвращаемое значение. TableSelect с выбранным порядком.

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

Получить дешёвые товары с устойчивым порядком при равной цене.

$select->orderby('price_cents ASC','id ASC')->limit(2);

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

Показать учебный журнал вставок в порядке явного ключа.

$select->orderby(['id ASC']);

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

Условия, ошибки и стоимость

Без уникального завершающего ключа одинаковые значения могут менять взаимный порядок. Это не обещание стабильного OFFSET при параллельных изменениях данных. Сортировка на сервере устраняет необходимость выгружать всё ради usort, но может требовать индекса/работы сервера. Пользователь выбирает код сортировки из allowlist, а не произвольную SQL-строку.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableSelect. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableSelect::limitОграничить число строк ответа до загрузки массива PHP.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableSelect::limit(int $rows): mysql_xdevapi\TableSelect

Ограничить число строк ответа до загрузки массива PHP.

Путь конкретного вызова
  1. Четыре строки прошли фильтр
  2. limit2 возвращает не больше двух
  3. fetchAll получает маленький массив выбранной страницы

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

TableSelect::limit(int $rows): mysql_xdevapi\TableSelect
Параметр или часть вызоваЗначение
rowsМаксимальное количество строк, например 2; не номер страницы.

Возвращаемое значение. TableSelect с верхней границей размера ответа.

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

Показать две карточки каталога.

$select->orderby('price_cents ASC','id ASC')->limit(2);

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

Ограничить карточку одной строкой, сохранив возможность отсутствия.

$table->select('id','sku')->where('id=:id')->bind(['id'=>999])->limit(1)->execute();

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

Условия, ошибки и стоимость

Ограничение без orderby не выбирает однозначный «первый» товар. Размер страницы проверяется приложением и имеет разумный верхний предел; огромный пользовательский limit отменяет пользу ограничения. LIMIT не означает, что сервер обработал ровно столько строк: фильтр, сортировка и группировка могут потребовать больше работы. Не загружайте всё ради array_slice.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableSelect. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableSelect::offsetПропустить заданное число строк упорядоченного результата.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableSelect::offset(int $position): mysql_xdevapi\TableSelect

Пропустить заданное число строк упорядоченного результата.

Путь конкретного вызова
  1. Порядок CLIP,CABLE,LED,STRIP
  2. offset2 пропускает CLIP/CABLE
  3. limit2 отдаёт LED/STRIP
  4. offset4 даёт пустую страницу

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

TableSelect::offset(int $position): mysql_xdevapi\TableSelect
Параметр или часть вызоваЗначение
positionСколько совпавших строк пропустить, считая с нулевого смещения.

Возвращаемое значение. TableSelect с выбранным смещением; в примерах применяется вместе с limit.

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

Открыть вторую страницу при размере 2.

$select->orderby('price_cents ASC','id ASC')->limit(2)->offset(2);

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

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

$select->orderby('price_cents ASC','id ASC')->limit(2)->offset(4);

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

Условия, ошибки и стоимость

Смещение — не индекс исходной физической строки таблицы: оно относится к результату where/orderby. Для page>=1 формула (page-1)*size требует проверки диапазона и переполнения. Большое смещение может быть дорогим; при меняющемся каталоге страницы сдвигаются. Сравните курсорную пагинацию по стабильным ключам для долгих списков.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableSelect. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\TableSelect::executeВыполнить собранное чтение и получить RowResult.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

TableSelect::execute(): mysql_xdevapi\RowResult

Выполнить собранное чтение и получить RowResult.

Путь конкретного вызова
  1. Builder содержит условие и поля
  2. execute выполняет серверный SELECT
  3. RowResult описывает колонки и выбранные строки

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

TableSelect::execute(): mysql_xdevapi\RowResult
Параметр или часть вызоваЗначение
ПараметрыНет; поля, фильтр, значения и порядок уже заданы.

Возвращаемое значение. RowResult — объект строк и metadata; отсутствие совпадений не делает его false.

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

Получить ограниченный массив для экрана.

$result = $select->limit(2)->execute();
$page = $result->fetchAll();

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

Различить успешное чтение без карточки и ошибку исполнения.

$result = $table->select('sku AS article')->where('id=:id')->bind(['id'=>999])->execute();
$row = $result->fetchOne();

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

Условия, ошибки и стоимость

execute ещё не означает, что вы вывели строки: нужно прочитать результат и сформировать HTML. Сам результат не создаётся через new. Сетевые/SQL-ошибки не маскируются пустым массивом; непустое metadata не является доказательством наличия строк. Повторный execute — новый запрос с потенциально изменившимися данными.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: TableSelect. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\RowResult::fetchOneПрочитать следующую строку и проверить конец результата.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

RowResult::fetchOne(): array

Прочитать следующую строку и проверить конец результата.

Путь конкретного вызова
  1. fetchOne → LED
  2. следующий fetchOne → CABLE
  3. затем STRIP
  4. ещё один → null

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

RowResult::fetchOne(): array
Параметр или часть вызоваЗначение
ПараметрыНет; читается выбранный RowResult.

Возвращаемое значение. Ассоциативный массив следующей строки либо null, когда строк больше нет. Краткий тип array в Manual не отражает этот null.

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

Показать первую карточку без обращения к полям null.

$first = $result->fetchOne();
if ($first !== null) {echo $first['article'];}

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

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

$rest = [];
while (($row = $result->fetchOne()) !== null) {$rest[] = $row;}

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

Условия, ошибки и стоимость

Каждый вызов продвигает позицию. Не проверяйте конец по значению конкретного поля: оно само может быть NULL или 0. Успешный запрос без строк и ошибка запроса различаются. В этом BUFFERED-профиле fetchAll не является способом получить остаток текущего fetchOne-курсора — это отдельно показано на соседней карточке.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: RowResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\RowResult::fetchAllПолучить массив строк буферизованного ответа.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

RowResult::fetchAll(): array

Получить массив строк буферизованного ответа.

Путь конкретного вызова
  1. Буфер содержит LED/CABLE/STRIP
  2. fetchOne уже отдал LED
  3. fetchAll всё равно содержит все три
  4. следующий fetchOne остаётся на CABLE

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

RowResult::fetchAll(): array
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Нумерованный массив ассоциативных строк; при отсутствии строк — []. В BUFFERED 8.0.30 возвращается весь буфер, включая ранее прочитанное fetchOne.

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

Один раз получить небольшой готовый список для шаблона каталога.

$page = $select->limit(2)->execute()->fetchAll();

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

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

$first = $result->fetchOne();
$allRows = $result->fetchAll();
$next = $result->fetchOne();

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

Условия, ошибки и стоимость

PHP Manual говорит обо всех строках, но не раскрывает подробно взаимодействие с fetchOne; реальный запуск делает его явным. Не переносите сюда правило PDO «fetchAll забирает остаток». Чтобы избежать повторов, выбирайте один основной способ чтения в бизнес-коде. Ограничивайте исходный запрос: fetchAll миллиона строк не становится экономным от того, что вызов короткий.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: RowResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\RowResult::getColumnsCountУзнать число полей выбранной проекции.

Переименование getColumnCount → getColumnsCount в PECL 8.0.14; проверен метод с s в 8.0.30. · mysql_xdevapi

RowResult::getColumnsCount(): int

Узнать число полей выбранной проекции.

Путь конкретного вызова
  1. select id AS product_id,sku AS article,price_cents AS cost_cents
  2. описание содержит три поля
  3. даже пустая выборка двух полей сохраняет count2

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

RowResult::getColumnsCount(): int
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Количество колонок результата; 0 при отсутствии колонок. Ноль строк не означает ноль колонок.

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

Проверить ширину таблицы отчёта.

$width = $result->getColumnsCount(); // 3 для выбранных трёх полей

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

Не потерять заголовки пустого экрана «ничего не найдено».

$emptyWidth = $empty->getColumnsCount(); // 2, хотя строк 0

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

Условия, ошибки и стоимость

С 8.0.14 прежний getColumnCount переименован в getColumnsCount. Старое единственное число осталось в URL официальной страницы, но copyable вызов использует действительное имя с s. Это число полей SELECT, а не всех колонок исходной таблицы и не количество товаров. Для заголовков нужна также getColumnNames.

Версии. Переименование getColumnCount → getColumnsCount в PECL 8.0.14; проверен метод с s в 8.0.30.

Контекст объекта: RowResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\RowResult::getColumnNamesПолучить имена колонок ответа, включая aliases.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

RowResult::getColumnNames(): array

Получить имена колонок ответа, включая aliases.

Путь конкретного вызова
  1. id AS product_id → имя product_id
  2. sku AS article → имя article
  3. метаданные дают заголовки до чтения строк

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

RowResult::getColumnNames(): array
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Нумерованный массив имён колонок результата. Имена соответствуют проекции, а не обязательно физическим именам таблицы.

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

Построить заголовки отчёта с понятными именами.

$names = $result->getColumnNames();
// product_id, article, cost_cents

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

Показать известные колонки и сообщение об отсутствии строк отдельно.

$emptyNames = $empty->getColumnNames();
$rows = $empty->fetchAll();

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

Условия, ошибки и стоимость

Manual описывает [] для пустого результата; фактический профиль 8.0.30 сохраняет metadata даже для SELECT с нулём строк. Это видимая граница формулировки, а не повод проверять товары по непустому массиву имён. Для HTML экранируйте названия. Имена/порядок нескольких одинаковых выражений без aliases могут быть неудобными; задавайте ясные уникальные aliases заранее.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: RowResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatement::bindДобавить очередное значение для позиционного знака вопроса.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatement::bind(string $param): mysql_xdevapi\SqlStatement

Добавить очередное значение для позиционного знака вопроса.

Путь конкретного вызова
  1. WHERE sku=? AND stock>=?
  2. первый bind LED занимает первое место
  3. второй bind 2 занимает второе
  4. execute сравнивает данные на сервере

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

SqlStatement::bind(string $param): mysql_xdevapi\SqlStatement
Параметр или часть вызоваЗначение
paramКороткая сигнатура Manual указывает string. Matching PECL 8.0.30 принимает один zval-аргумент; рабочие примеры передают строку или целое число отдельным вызовом.

Возвращаемое значение. SqlStatement для цепочки следующих bind/execute; это не выполнение запроса.

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

Передать артикул и порог остатка без склейки с SQL.

$query->bind('LED')->bind(2);

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

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

$query = $session->sql('SELECT sku FROM '.$qualified.' WHERE sku IN (?,?) ORDER BY id');
$rows = $query->bind('CABLE')->bind('LED')->execute()->fetchAll();

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

Условия, ошибки и стоимость

В PECL mysql_xdevapi 8.0.30 это не bind($a,$b) и не гарантированное распаковывание массива bind([$a,$b]); такие формы нельзя перенести из другого коннектора. Количество и порядок должны соответствовать ?. Значение не становится именем таблицы или выражением ORDER BY. При повторном использовании Statement учитывайте его состояние и результаты; в самостоятельных сценариях новый запрос получает новый объект.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatement. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatement::executeВыполнить SQL и получить фактический объект результата.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatement::execute(): mysql_xdevapi\Result

Выполнить SQL и получить фактический объект результата.

Путь конкретного вызова
  1. Сформирован SQL и его bind-значения
  2. execute отправляет операцию
  3. результат содержит строки либо квитанцию
  4. прикладной код читает подходящий вид ответа

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

SqlStatement::execute(): mysql_xdevapi\Result
Параметр или часть вызоваЗначение
Параметры ManualСтраница показывает отсутствие аргументов и не заполнена подробно.
Фактический 8.0.30В исходнике допустим необязательный целочисленный flags; default SqlStatement::BUFFERED. Здесь используется execute() без смены режима.

Возвращаемое значение. В опубликованной сигнатуре указан Result; фактические SELECT/INSERT/UPDATE в этом профиле дают SqlStatementResult. Тип сохранён в наблюдениях.

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

Выполнить фильтр каталога и получить строки.

$rows = $session->sql($sql)->bind('LED')->bind(2)->execute()->fetchAll();

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

Выполнить изменение и прочитать affected count, не ожидать SELECT-строку.

$receipt = $session->sql('UPDATE '.$qualified.' SET name=? WHERE sku=?')
    ->bind('Новое имя')->bind('NO-SUCH')->execute();

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

Условия, ошибки и стоимость

Короткую сигнатуру Manual сохраняем, но не объявляем её полной для реализации 8.0.30. EXECUTE_ASYNC и multi-result требуют отдельного жизненного цикла, поэтому не включаются в эти синхронные примеры. Возвращение объекта не гарантирует непустые строки. Ошибка SQL/параметров/доступа должна быть обработана как отказ; повтор не является автоматически безопасной операцией.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatement. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatementResult::hasDataПроверить, доступна ли очередная строка для курсорного чтения.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatementResult::hasData(): bool

Проверить, доступна ли очередная строка для курсорного чтения.

Путь конкретного вызова
  1. SELECT одной строки: до чтения true
  2. fetchOne отдаёт LED
  3. после этой строки false
  4. INSERT и пустой SELECT тоже false

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

SqlStatementResult::hasData(): bool
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. В описании Manual — true, если есть данные. В реализации 8.0.30 это проверка !eof текущего результата, которая меняется при fetchOne.

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

Показать переход от доступной карточки к концу результата.

$before = $read->hasData();
$row = $read->fetchOne();
$after = $read->hasData();

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

Понять, почему false не позволяет угадать тип выполненной операции.

$insertHasRows = $insert->hasData();
$emptyHasRows = $empty->hasData();

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

Условия, ошибки и стоимость

hasData не является неизменным флагом «это SELECT» и не заменяет обработку ошибок. В BUFFERED-профиле false после fetchOne не означает, что fetchAll потерял сохранённый массив: он всё ещё отдаёт весь буфер. Выберите понятную стратегию чтения и не принимайте два разных механизма за один счётчик.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatementResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatementResult::fetchOneПолучить следующую строку обычного SQL SELECT.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatementResult::fetchOne(): array

Получить следующую строку обычного SQL SELECT.

Путь конкретного вызова
  1. SQL WHERE id=? связывает ID квитанции
  2. fetchOne возвращает поля карточки
  3. после единственной строки следующий fetchOne даёт null

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

SqlStatementResult::fetchOne(): array
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Ассоциативный массив следующей строки; null при отсутствии дальнейших строк, несмотря на краткий тип array в сигнатуре.

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

Прочитать именно созданный товар по фактическому ID.

$saved = $session->sql('SELECT id,sku,name FROM '.$qualified.' WHERE id=?')->bind($id)->execute()->fetchOne();

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

Проверить отсутствие точного артикула, а не ошибочно читать поле у null.

$result = $session->sql($sql)->bind("LED' OR 1=1 --")->bind(2)->execute();
$row = $result->fetchOne();

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

Условия, ошибки и стоимость

Ключи задаются именами колонок/aliases SELECT. Для JOIN задавайте разные aliases, чтобы одинаковые имена не портили форму ответа. Чтение продвигает курсор; исполнение запроса и чтение строки — разные действия. Для DML сначала смотрите квитанцию, а не пытайтесь трактовать отсутствие строки как провал INSERT.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatementResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatementResult::fetchAllПолучить массив строк SQL-ответа в буферизованном режиме.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatementResult::fetchAll(): array

Получить массив строк SQL-ответа в буферизованном режиме.

Путь конкретного вызова
  1. SELECT вернул LED
  2. fetchOne прочитал LED и изменил hasData
  3. fetchAll всё ещё возвращает массив с LED
  4. пустой отдельный SELECT возвращает []

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

SqlStatementResult::fetchAll(): array
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Нумерованный массив ассоциативных строк, [] при отсутствии строк; в BUFFERED 8.0.30 весь буфер, а не только остаток fetchOne.

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

Получить маленькую выборку для отчёта целиком.

$rows = $session->sql($sql)->bind('LED')->bind(2)->execute()->fetchAll();

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

Увидеть сохранение полного буфера после курсорного чтения.

$first = $read->fetchOne();
$buffer = $read->fetchAll();

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

Условия, ошибки и стоимость

Не складывайте fetchOne и fetchAll как независимые части списка: здесь первая строка окажется повторена. Для большого отчёта заранее выбирайте размер порции и стратегию выгрузки, а не удерживайте всё в PHP. [] не является сообщением о потере соединения; ошибки execute не подменяются успешным пустым результатом.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatementResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatementResult::getAffectedItemsCountПрочитать квитанцию о числе затронутых строк SQL-операции.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatementResult::getAffectedItemsCount(): int

Прочитать квитанцию о числе затронутых строк SQL-операции.

Путь конкретного вызова
  1. INSERT одного LED → affected 1
  2. UPDATE NO-SUCH → affected 0
  3. для SELECT число прочитанных строк проверяется отдельно

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

SqlStatementResult::getAffectedItemsCount(): int
Параметр или часть вызоваЗначение
ПараметрыНет.

Возвращаемое значение. Счётчик из результата SQL. В исходнике 8.0.30 значения от PHP_INT_MAX включительно возвращаются строкой; пример работает с малыми точными числами.

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

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

$added = $insert->getAffectedItemsCount();

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

Обнаружить отсутствие изменения по запросу, затем разобраться в его причине.

$changed = $missingUpdate->getAffectedItemsCount();

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

Условия, ошибки и стоимость

Счётчик не равен размеру таблицы и не является переносимым способом считать SELECT. Нулевой UPDATE может означать отсутствие совпадений либо отсутствие фактического изменения по условиям/режиму API; без повторного чтения и предметного контекста нельзя написать «ID не существует». Для конфликтов версии смотрите следующий сценарий редактирования, а не автоматически повторяйте запрос.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatementResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

mysql_xdevapi\SqlStatementResult::getLastInsertIdПолучить AUTO_INCREMENT идентификатор из квитанции нужного INSERT.

На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13. · mysql_xdevapi

SqlStatementResult::getLastInsertId(): String

Получить AUTO_INCREMENT идентификатор из квитанции нужного INSERT.

Путь конкретного вызова
  1. INSERT LED назначает id на сервере
  2. квитанция возвращает этот id
  3. новый SELECT WHERE id=? читает LED
  4. реальный ID записан в JSON, в выводе проверяется связь

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

SqlStatementResult::getLastInsertId(): String
Параметр или часть вызоваЗначение
ПараметрыНет; берётся результат конкретной SQL-операции.

Возвращаемое значение. Manual обещает ID последней вставки строкой. Matching 8.0.30 возвращает int для значения ниже PHP_INT_MAX, строку от PHP_INT_MAX включительно; при отсутствии execution state исходник начинает с false.

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

Открыть карточку только что созданного товара.

$id = $insert->getLastInsertId();
$saved = $session->sql('SELECT id,sku,name FROM '.$qualified.' WHERE id=?')->bind($id)->execute()->fetchOne();

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

Проверить соответствие без опасного приведения большого ID к PHP int.

$idType = get_debug_type($id);
$isSame = (string)$saved['id'] === (string)$id;

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

Условия, ошибки и стоимость

Не заменяйте квитанцию SELECT MAX(id): параллельная вставка может дать чужой максимум. Не ожидайте подряд идущие ID без пропусков и не угадывайте список ID массового INSERT прибавлением единицы. Generated IDs JSON-документов — другой механизм. Для SQL без AUTO_INCREMENT результат нельзя выдавать за адрес новой карточки без соответствующего контракта.

Версии. На странице метода первая версия не указана; проверяемый профиль PECL 8.0.30 / PHP 8.3.35 / MySQL 8.4.13.

Контекст объекта: SqlStatementResult. Дальше используйте полную последовательность действий, где видно исходные данные, результат операции и его чтение.

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

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

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

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

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