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

Дерево руководства
Глава 134 · После первых коллекций, таблиц и результатов X DevAPI
MySQL X DevAPI: подтверждение записи, настоящие ID и предупреждения
Отличать ID документов от AUTO_INCREMENT, читать настоящие предупреждения и подтверждать запись или отказ после полного ответа.
Как понять, что действительно сохранилось
После нажатия «Сохранить» пользователю нужна уверенность: какой заказ создан, какая сумма записана и почему повторный запрос был отклонён. Одного true или числа затронутых строк недостаточно для всех этих ответов. В этой главе создадим ORDER-101, обнаружим округление 12.345 до 12.35 и сохраним исходные 12.34 при повторном ORDER-A с другой суммой.
- PHP подготовил значения заказа
- Команда отправлена: сервер ещё должен ответить
- Ответ: запись / предупреждение / отказ
- Readback: конкретный ID, сумма и сохранённая строка
Квитанция — объект с итогами одной операции. Она не является самой таблицей и не обязательно содержит строки. Readback — повторное чтение именно записанных данных. Аналогия с кассой помогает различить отправленный запрос и полученный чек, но не означает, что любой успех автоматически фиксирует открытую транзакцию: commit остаётся отдельным действием.
| Нужно узнать | Подходящий ответ | Что он сам по себе не доказывает |
|---|---|---|
| ID нового SQL-заказа | SqlStatementResult::getLastInsertId | Список строковых _id документов |
| ID документов без собственного _id | Result::getGeneratedIds | Непрерывность последовательности чисел |
| Причину округления | getWarnings: настоящие Warning | Совпадение исходного и сохранённого значения |
| Сервер принял async-запись | Прочитанный ответ, события, отсутствие ошибки | Успех по одному факту отправки |
| Что осталось после конфликта | SELECT нужного заказа | Только число вызовов callback |
Перед примерами нужны функции, аргументы и return, массивы, объекты и методы и соединение X DevAPI. Таблицы и параметризованные запросы объясняет SQL-основу. Здесь каждый новый способ ответа сначала разбирается короткой записью, а полный файл добавляет локальные данные и уборку.
Callback — переданная библиотеке функция, которая вызывается при событии. В двух последних программах обработчики складывают события в один объект контекста: строка, предупреждение, отказ, конец набора или успешное завершение. Контекст создало приложение; его нельзя выдавать за ExecutionStatus. Поле events показывает последовательность, а отдельный SELECT — итог в базе.
- execute(EXECUTE_ASYNC) → true: отправлена
- getResult → серверная ошибка 1062
- Нет statement_ok, итог чтения false
- Исходный ORDER-A остаётся 12.34
В учебном профиле PHP 8.3.35, mysql_xdevapi 8.0.30 и MySQL 8.4.13 callback успешного завершения получает один context. ExecutionStatus существует как класс с закрытым конструктором, но callback не получает его вторым аргументом. Это установлено реальным ограниченным запуском и сопоставлено с исходником. Официальные страницы класса и конструктора сохранены и объяснены; рабочая альтернатива — обычный Result или SqlStatementResult. Ни обход видимости, ни имитация ответа не используются.
Архив каждого урока содержит example.php, lab.php и README.txt. Нужен подготовленный локальный MySQL с X Protocol и расширением mysql_xdevapi; URI и имя схемы берутся из PHPMAN_MYSQLX_URI и PHPMAN_MYSQLX_SCHEMA. Программа создаёт только свои объекты mysql145_, сохраняет result.html и result.json и удаляет только свои объекты в базе; файлы результата остаются. В JSON остаются настоящие ID, сообщения, типы, readback и список очищенных объектов; metrics.json содержит сводные проверки. Случайные имена и ID не подставляются в ожидаемый вывод как константы.
Все учебные записи выполняются в autocommit. Это не готовые HTTP-обработчики и не обращение к внешней платёжной системе. Для нескольких связанных изменений сначала изучите предметную транзакцию. В обычном приложении начинайте с обычного execute; расширенный callback-путь нужен там, где вы понимаете порядок чтения ответа и проверили поддержку своего профиля.
Связать созданный документ и SQL-заказ с их собственными ID
После создания товара нужно открыть его карточку, а после записи заказа — сохранить ссылку на этот заказ. В коллекции документов сервер создаёт строковый _id; в нашей SQL-таблице — целый AUTO_INCREMENT. Научимся брать каждый идентификатор из правильного ответа и затем действительно находить созданные данные.
- Collection::add без _id → Result → один generated document ID
- find(_id = полученный ID) → LED, qty 2
- SQL INSERT ORDER-101 → SqlStatementResult → getLastInsertId
- SELECT id = полученный номер → ORDER-101, qty 2
Небольшая запись и её смысл
ID — значение, по которому запись можно однозначно найти. В строке SQL знак ? обозначает место значения; последовательные bind передают номер заказа и количество. $qualifiedTable уже содержит процитированное имя собственной таблицы; полный файл получает его через x145Qualified. execute возвращает объект ответа. getLastInsertId читает AUTO_INCREMENT этой вставки, а getGeneratedIds читает список сгенерированных документных _id. Пустой массив [] — список без элементов, а не сообщение об ошибке. В первой части полного файла add получает массив без ключа _id; после execute возвращённый ID передаётся в find через bind. Мы проверяем совпадение идентификаторов и содержимого, а не заранее придумываем серверный UUID.
$receipt = $session->sql('INSERT INTO ' . $qualifiedTable . ' (order_ref,qty) VALUES (?,?)')
->bind('ORDER-101')->bind(2)->execute();
$orderId = $receipt->getLastInsertId();
$documentIds = $receipt->getGeneratedIds(); // Для этого SQL INSERT: [].Что получается на исходных данных
| Наблюдение | Результат |
|---|---|
| Сгенерировано ID документов | 1 |
| Документ прочитан по полученному ID | да |
| SQL getGeneratedIds: элементов | 0 |
| SQL-заказ прочитан по getLastInsertId | да |
| Сохранённый заказ | ORDER-101 |
| Количество заказа | 2 |
Самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x145Config();
$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']];
$collectionName=x145OwnCollection($schema,$owned,'generated_ids');
$documents=$schema->getCollection($collectionName);
$documentReceipt=$documents->add(['sku'=>'LED','qty'=>2])->execute();
$documentIds=$documentReceipt->getGeneratedIds();
if (count($documentIds)!==1 || !is_string($documentIds[0]) || $documentIds[0]==='') {
throw new RuntimeException('Сервер не вернул один строковый ID документа.');
}
$savedDocumentRows=$documents->find('_id = :id')->bind(['id'=>$documentIds[0]])->execute()->fetchAll();
$name=x145OwnTable($session,$schema,$owned,'order_ids',
'(id INT AUTO_INCREMENT PRIMARY KEY, order_ref VARCHAR(30) NOT NULL UNIQUE, qty INT NOT NULL) ENGINE=InnoDB');
$qualified=x145Qualified($schema,$name);
$sqlReceipt=$session->sql('INSERT INTO '.$qualified.' (order_ref,qty) VALUES (?,?)')
->bind('ORDER-101')->bind(2)->execute();
$sqlGeneratedIds=$sqlReceipt->getGeneratedIds();
$insertId=$sqlReceipt->getLastInsertId();
$savedOrderRows=$session->sql('SELECT id,order_ref,qty FROM '.$qualified.' WHERE id = ?')
->bind($insertId)->execute()->fetchAll();
$documentMatches=count($savedDocumentRows)===1
&& $savedDocumentRows[0]['_id']===$documentIds[0]
&& $savedDocumentRows[0]['sku']==='LED' && $savedDocumentRows[0]['qty']===2;
$orderMatches=count($savedOrderRows)===1 && $savedOrderRows[0]['id']===$insertId
&& $savedOrderRows[0]['order_ref']==='ORDER-101' && $savedOrderRows[0]['qty']===2;
$rows=[['Сгенерировано ID документов',count($documentIds)],
['Документ прочитан по полученному ID',$documentMatches],
['SQL getGeneratedIds: элементов',count($sqlGeneratedIds)],
['SQL-заказ прочитан по getLastInsertId',$orderMatches],
['Сохранённый заказ',$savedOrderRows[0]['order_ref']],
['Количество заказа',$savedOrderRows[0]['qty']]];
$checks=['document_id_readback'=>$documentMatches,'sql_id_readback'=>$orderMatches && $insertId>0,
'different_id_channels'=>$sqlGeneratedIds===[] && $sqlReceipt instanceof mysql_xdevapi\SqlStatementResult
&& $documentReceipt instanceof mysql_xdevapi\Result];
$observations=['document_generated_ids'=>$documentIds,'document_rows'=>$savedDocumentRows,
'sql_generated_ids'=>$sqlGeneratedIds,'sql_last_insert_id'=>$insertId,'sql_last_insert_id_type'=>get_debug_type($insertId),
'order_rows'=>$savedOrderRows,'document_receipt_type'=>get_debug_type($documentReceipt),
'sql_receipt_type'=>get_debug_type($sqlReceipt)];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x145Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x145Report('mysql145-x-generated-id-kinds', 'Связать созданный документ и SQL-заказ с их собственными ID', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Сгенерировано ID документов — 1
Документ прочитан по полученному ID — да
SQL getGeneratedIds: элементов — 0
SQL-заказ прочитан по getLastInsertId — да
Сохранённый заказ — ORDER-101
Количество заказа — 2
CLI-программа создаёт собственные учебные объекты в подготовленной локальной схеме, сохраняет результат и настоящую диагностику, затем удаляет только эти объекты. Это самостоятельный пример, не обработчик формы.
Таблица фактического запуска · Исходные данные и диагностика JSON.
Границы и типичные ошибки
Название getGeneratedIds не означает «все виды автоматически созданных номеров». В PECL 8.0.30 метод SqlStatementResult копирует именно generated_doc_ids; SQL AUTO_INCREMENT имеет отдельный getLastInsertId. Вставка документа с явно указанным _id не обязана добавлять его в список сгенерированных ID. Сохраняйте ответ нужной операции, а не ищите MAX(id): другой клиент может вставить строку между запросами. Числовые ID не обязаны идти без пропусков. Полный файл использует маленькое значение INT; большие идентификаторы требуют сохранения исходного типа без безусловного приведения к int. Схема создаётся заранее; пример не создаёт базу и не меняет права.
Измените требование
Добавьте в SQL-заказ строку с order_ref ORDER-102. Как получить именно её ID и не перепутать с ORDER-101?
Разбор следующего шага
Сохраните новый SqlStatementResult в другой переменной, прочитайте его getLastInsertId и выполните SELECT по этому значению. Не используйте прежний ответ или MAX(id). Для документного товара продолжайте хранить строковый _id из его собственной квитанции.
Увидеть округление цены вместе с настоящим предупреждением
Поставщик прислал цену 12.345, а каталог хранит два знака после запятой. Запись может успешно завершиться с ценой 12.35: «вставлена одна строка» не означает точного сохранения исходного значения. Покажем исходную цену, сохранённую цену и объект Warning, который объясняет преобразование.
- Ввод: строка 12.345 → DECIMAL(8,2)
- INSERT выполнен → 1 строка и Note 1265
- getWarnings → Warning: code, level, message
- SELECT сохранённой цены → 12.35
Небольшая запись и её смысл
DECIMAL(8,2) хранит точное десятичное число с двумя знаками после запятой и общим пределом восемь цифр. getWarnings возвращает массив объектов Warning: foreach берёт по одному объекту, ->code читает числовой код, ->message — объяснение сервера. ->level хранит уровень, в нашем случае 1 — Note. Эти объекты создаёт расширение из ответа, new Warning не нужен и запрещён закрытым конструктором. Перед readback сохраняем предупреждения в переменные. В полном файле цена передаётся строкой, чтобы PHP сначала не превращал её в неточное двоичное float.
$receipt = $statement->execute();
$warnings = $receipt->getWarnings();
foreach ($warnings as $warning) {
echo $warning->code, ': ', $warning->message, PHP_EOL;
}Что получается на исходных данных
| Наблюдение | Результат |
|---|---|
| Переданная цена | 12.345 |
| Сохранённая цена | 12.35 |
| Вставлено строк | 1 |
| Предупреждений | 1 |
| Объект предупреждения | mysql_xdevapi\Warning |
| Код | 1265 |
| Уровень | 1 |
Самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x145Config();
$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=x145OwnTable($session,$schema,$owned,'price_review',
'(id INT PRIMARY KEY, sku VARCHAR(20) NOT NULL, price DECIMAL(8,2) NOT NULL) ENGINE=InnoDB');
$qualified=x145Qualified($schema,$name);
$inputPrice='12.345';
$receipt=$session->sql('INSERT INTO '.$qualified.' (id,sku,price) VALUES (?,?,?)')
->bind(1)->bind('LED')->bind($inputPrice)->execute();
$warnings=$receipt->getWarnings();
$warningCount=$receipt->getWarningsCount();
$warningRows=[];
foreach ($warnings as $warning) {
$warningRows[]=['type'=>get_debug_type($warning),'code'=>$warning->code,
'level'=>$warning->level,'message'=>$warning->message];
}
$saved=$session->sql('SELECT sku,price FROM '.$qualified.' WHERE id = ?')->bind(1)->execute()->fetchAll();
$rows=[['Переданная цена',$inputPrice],['Сохранённая цена',$saved[0]['price']],
['Вставлено строк',$receipt->getAffectedItemsCount()],['Предупреждений',$warningCount],
['Объект предупреждения',$warningRows[0]['type']],['Код',$warningRows[0]['code']],
['Уровень',$warningRows[0]['level']]];
$checks=['warning_is_native'=>count($warnings)===1 && $warnings[0] instanceof mysql_xdevapi\Warning,
'rounding_reported'=>$warningCount===1 && $warningRows[0]['code']===1265 && $warningRows[0]['level']===1,
'price_readback'=>$saved===[['sku'=>'LED','price'=>'12.35']] && $receipt->getAffectedItemsCount()===1,
'warning_message_present'=>is_string($warningRows[0]['message']) && $warningRows[0]['message']!==''];
$observations=['input_price'=>$inputPrice,'warning_count'=>$warningCount,'warnings'=>$warningRows,
'saved_rows'=>$saved,'saved_price_type'=>get_debug_type($saved[0]['price']),
'affected'=>$receipt->getAffectedItemsCount(),'sql_mode'=>$session->sql('SELECT @@SESSION.sql_mode AS mode')->execute()->fetchAll()];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x145Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x145Report('mysql145-x-warning-object-review', 'Увидеть округление цены вместе с настоящим предупреждением', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Переданная цена — 12.345
Сохранённая цена — 12.35
Вставлено строк — 1
Предупреждений — 1
Объект предупреждения — mysql_xdevapi\Warning
Код — 1265
Уровень — 1
CLI-программа создаёт собственные учебные объекты в подготовленной локальной схеме, сохраняет результат и настоящую диагностику, затем удаляет только эти объекты. Это самостоятельный пример, не обработчик формы.
Таблица фактического запуска · Исходные данные и диагностика JSON.
Границы и типичные ошибки
Округление дробной части при записи в точный тип может выдавать Note 1265 даже в строгом SQL-режиме. Это не универсальное правило для любой обрезки строки или выхода за диапазон: там возможна ошибка. Сохраняйте код, уровень и исходное сообщение вместе; сообщение может содержать имя собственной таблицы, поэтому его полный текст записан в JSON, а не закреплён как постоянный текст stdout. SHOW WARNINGS относится к последней подходящей команде сессии; несвязанный следующий запрос может сменить диагностику. Здесь Warning берётся из сохранённого результата операции. Если бизнес-правило запрещает третью дробную цифру, валидируйте цену до записи или используйте предметную транзакцию и явный отказ; не скрывайте преобразование успешным статусом.
В обсуждении человек не замечал предупреждения после изменений данных. Это причина показывать результат преобразования и диагностику вместе. Старые настройки phpMyAdmin здесь не используются; правила диагностики взяты из MySQL и ответа нашего расширения. Обсуждение: Предупреждения потерялись за сообщением о вставленных строках.
Измените требование
Поставщик передал 12.34. Какие строки отчёта должны измениться?
Разбор следующего шага
Цена после записи останется 12.34, а Note об округлении больше не требуется. В варианте общего обработчика сначала проверяйте число предупреждений, а затем перебирайте массив: нельзя безусловно читать warnings[0], если список пуст. В текущем сценарии одна конкретная цена специально вызывает предупреждение для объяснения.
Отделить отправку заказа от подтверждения и сохранённой суммы
Сервис не должен отвечать «заказ сохранён» сразу после отправки команды. Между отправкой и завершением сервер ещё может отклонить запись или округлить сумму. Сначала покажем обычную квитанцию, затем проследим ответ на асинхронно отправленный ORDER-CALLBACK: предупреждение, подтверждение и сохранённые 3.46.
- execute(EXECUTE_ASYNC) → true: команда отправлена
- getResult(callbacks) → warning 1265, затем statement_ok
- В PECL 8.0.30 callback получил один context, без ExecutionStatus
- После завершения SELECT → ORDER-CALLBACK, 3.46; обычный ответ → affected 1
Небольшая запись и её смысл
Первая запись специально использует обычный execute(): он возвращает SqlStatementResult, из которого можно прочитать число вставленных строк и AUTO_INCREMENT. Для большинства обработчиков этого достаточно. Callback — функция, которую библиотека вызывает при получении события; наша readOperation передаёт пять таких функций и объект контекста для накопления событий. EXECUTE_ASYNC — константа класса SqlStatement: её значение 1 означает отправку без немедленного чтения результата. Затем getResult читает ответ и вызывает функции. ...$arguments распаковывает массив аргументов при вызове; ...$args в объявлении onOk, наоборот, собирает реально переданные аргументы. Так программа не придумывает второй аргумент, а показывает фактический контракт установленного расширения. Пока ответ читается, обработчики только сохраняют данные в контекст и не запускают новые запросы.
$receipt = $session->sql('INSERT INTO ' . $qualifiedTable . ' (order_ref,amount) VALUES (?,?)')
->bind('ORDER-NORMAL')->bind('12.34')->execute();
$inserted = $receipt->getAffectedItemsCount();
$orderId = $receipt->getLastInsertId();Что получается на исходных данных
| Наблюдение | Результат |
|---|---|
| Команда отправлена | да |
| Ответ записи прочитан | да |
| События записи | warning → statement_ok |
| Аргументов callback успешного завершения | 1 |
| Объект ExecutionStatus передан | нет |
| Сохранённая сумма ORDER-CALLBACK | 3.46 |
| Обычный ответ: вставлено строк | 1 |
| Следующий запрос выполнен | да |
Самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x145Config();
$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']];
function readOperation(mysql_xdevapi\SqlStatement $statement): array {
$context=(object)['events'=>[],'rows'=>[],'warnings'=>[],'errors'=>[],
'ends'=>[],'ok_calls'=>[]];
$onRow=static function ($ctx,array $row,array $columns) use ($context): void {
$context->events[]='row';
$context->rows[]=['same_context'=>$ctx===$context,'row'=>$row,
'column_types'=>array_map(static fn($column)=>get_debug_type($column),$columns)];
};
$onWarning=static function ($ctx,int $code,string $message) use ($context): void {
$context->events[]='warning';
$context->warnings[]=['same_context'=>$ctx===$context,'code'=>$code,
'message'=>x145SafeMessage($message)];
};
$onError=static function ($ctx,int $code,string $sqlstate,string $message) use ($context): void {
$context->events[]='error';
$context->errors[]=['same_context'=>$ctx===$context,'code'=>$code,
'sqlstate'=>$sqlstate,'message'=>x145SafeMessage($message)];
};
$onEnd=static function ($ctx,bool $hasMore) use ($context): void {
$context->events[]='resultset_end';
$context->ends[]=['same_context'=>$ctx===$context,'has_more'=>$hasMore];
};
$onOk=static function (...$args) use ($context): void {
$context->events[]='statement_ok';
$types=array_map(static fn($value)=>get_debug_type($value),$args);
$context->ok_calls[]=['argc'=>count($args),'types'=>$types,
'same_context'=>isset($args[0]) && $args[0]===$context,
'execution_status_received'=>in_array('mysql_xdevapi\ExecutionStatus',$types,true)];
};
$arguments=[$onRow,$onWarning,$onError,$onEnd,$onOk,$context];
$sent=$statement->execute(mysql_xdevapi\SqlStatement::EXECUTE_ASYNC);
if ($sent!==true) {throw new RuntimeException('Не удалось отправить команду.');}
$reads=[['method'=>'getResult','value'=>$statement->getResult(...$arguments)]];
$more=[$statement->hasMoreResults()];
while ($more[array_key_last($more)]) {
if (count($reads)>=4) {throw new RuntimeException('Ответ содержит больше четырёх ожидаемых частей.');}
$reads[]=['method'=>'getNextResult','value'=>$statement->getNextResult(...$arguments)];
$more[]=$statement->hasMoreResults();
}
return ['sent'=>$sent,'reads'=>$reads,'has_more'=>$more,'context'=>$context];
}
$name=x145OwnTable($session,$schema,$owned,'confirmed_orders',
'(id INT AUTO_INCREMENT PRIMARY KEY, order_ref VARCHAR(30) NOT NULL UNIQUE, amount DECIMAL(8,2) NOT NULL) ENGINE=InnoDB');
$qualified=x145Qualified($schema,$name);
$normalReceipt=$session->sql('INSERT INTO '.$qualified.' (order_ref,amount) VALUES (?,?)')
->bind('ORDER-NORMAL')->bind('12.34')->execute();
$normalId=$normalReceipt->getLastInsertId();
$normalAffected=$normalReceipt->getAffectedItemsCount();
$statement=$session->sql('INSERT INTO '.$qualified.' (order_ref,amount) VALUES (?,?)')
->bind('ORDER-CALLBACK')->bind('3.456');
$outcome=readOperation($statement);
$context=$outcome['context'];
$saved=$session->sql('SELECT order_ref,amount FROM '.$qualified.' ORDER BY id')->execute()->fetchAll();
$control=$session->sql('SELECT 1 AS ready')->execute()->fetchOne();
$ok=$context->ok_calls[0] ?? null;
if ($ok===null) {throw new RuntimeException('Не получено подтверждение успешного завершения.');}
$rows=[['Команда отправлена',$outcome['sent']],
['Ответ записи прочитан',$outcome['reads'][0]['value']],
['События записи',implode(' → ',$context->events)],
['Аргументов callback успешного завершения',$ok['argc']],
['Объект ExecutionStatus передан',$ok['execution_status_received']],
['Сохранённая сумма ORDER-CALLBACK',$saved[1]['amount']],
['Обычный ответ: вставлено строк',$normalAffected],
['Следующий запрос выполнен',$control['ready']===1]];
$checks=['callback_success_consumed'=>$outcome['sent']===true && $outcome['reads']===[['method'=>'getResult','value'=>true]]
&& $outcome['has_more']===[false] && $context->errors===[],
'actual_callback_arguments'=>$ok!==null && $ok['argc']===1 && $ok['types']===['stdClass']
&& $ok['same_context']===true && $ok['execution_status_received']===false,
'warning_and_completion'=>$context->events===['warning','statement_ok'] && count($context->warnings)===1
&& $context->warnings[0]['code']===1265 && $context->warnings[0]['same_context']===true,
'normal_receipt_available'=>$normalReceipt instanceof mysql_xdevapi\SqlStatementResult && $normalAffected===1 && $normalId>0,
'saved_amounts_exact'=>$saved===[['order_ref'=>'ORDER-NORMAL','amount'=>'12.34'],['order_ref'=>'ORDER-CALLBACK','amount'=>'3.46']],
'session_ready'=>$control===['ready'=>1]];
$observations=['callback_outcome'=>$outcome,'normal_receipt'=>['type'=>get_debug_type($normalReceipt),
'affected'=>$normalAffected,'last_insert_id'=>$normalId],'saved_rows'=>$saved,'control'=>$control];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x145Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x145Report('mysql145-x-callback-write-receipt', 'Отделить отправку заказа от подтверждения и сохранённой суммы', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Команда отправлена — да
Ответ записи прочитан — да
События записи — warning → statement_ok
Аргументов callback успешного завершения — 1
Объект ExecutionStatus передан — нет
Сохранённая сумма ORDER-CALLBACK — 3.46
Обычный ответ: вставлено строк — 1
Следующий запрос выполнен — да
CLI-программа создаёт собственные учебные объекты в подготовленной локальной схеме, сохраняет результат и настоящую диагностику, затем удаляет только эти объекты. Это самостоятельный пример, не обработчик формы.
Таблица фактического запуска · Исходные данные и диагностика JSON.
Границы и типичные ошибки
В PHP Manual класс ExecutionStatus описан с пятью публичными свойствами и закрытым конструктором, но в проверенном PECL 8.0.30 обработчик statement_ok получает только context. Исходник создаёт ExecutionStatus вторым элементом params, однако передаёт param_count=1. Поэтому пример честно выводит «ExecutionStatus передан — нет» и использует обычный SqlStatementResult как рабочую альтернативу для счётчика и ID. Reflection или самодельный DTO не превратят этот отсутствующий ответ в настоящий объект расширения. Нативный callback-путь getResult принимает шесть аргументов, хотя краткая официальная страница показывает вызов без параметров; это особенность конкретного профиля, требующая проверки при смене версии. Callback warning передаёт code и message, но не level и не Warning. Для уровня используйте обычное getWarnings. Отправка true не заменяет чтение; успех callback не заменяет commit открытой транзакции. Здесь работает autocommit, а все данные учебные. Не используйте ASYNC как обещание ускорения или как фоновую очередь.
Разработчик путал callback строки, завершение вставки и ответ HTTP. Вопрос относится к Node.js, поэтому его Promise-синтаксис не переносим в PHP. Полезна сама задача: подтверждение должно опираться на завершившуюся операцию. Здесь это показано через реальный SQL-ответ, события и readback. Обсуждение: Когда X DevAPI-обработчик может сообщить об успешной записи.
Измените требование
Нужно сформировать HTTP-ответ с ID созданного заказа и числом вставленных строк. Какой путь выбрать для обычного обработчика?
Разбор следующего шага
Выберите обычный execute(), сохраните SqlStatementResult, получите getLastInsertId/getAffectedItemsCount и обработайте исключение. При необходимости проверьте предупреждения и зафиксируйте предметную транзакцию до ответа. Callback-путь полезен для разбора событий, но в этом профиле не выдаёт ExecutionStatus. Отвечать успешно только на основании результата отправки нельзя.
Отклонить повторный заказ и сохранить исходные данные
Повторная доставка сообщения пытается второй раз создать ORDER-A, теперь с суммой 99.99. Уникальность номера заказа должна сохранить исходные 12.34. Увидим одновременно два факта: команда успешно отправлена, но сервер её отклонил; после чтения отказа соединение снова выполняет запросы.
- В таблице ORDER-A = 12.34, номер UNIQUE
- Повторный INSERT ORDER-A = 99.99 → отправка true
- getResult → error 1062 / 23000, результат false, без statement_ok
- SELECT → одна исходная строка 12.34; следующий запрос ready 1
Небольшая запись и её смысл
UNIQUE — ограничение, которое не разрешает второй строке получить тот же номер заказа. Код 1062 описывает конфликт дубликата, SQLSTATE 23000 — класс нарушения целостности. Callback onError получает эти данные отдельными аргументами; сообщение сохраняем с удалением возможных секретов через x145SafeMessage. Оно не подменяется заранее придуманным текстом. Короткая запись предполагает подготовленные пять обработчиков; полный файл показывает их объявления в readOperation. getResult возвращает false при данном отказе, хотя execute вернул true при отправке. После чтения проверяем hasMoreResults; только после полного ответа запускаем SELECT и показываем сохранившуюся строку.
$sent = $statement->execute(mysql_xdevapi\SqlStatement::EXECUTE_ASYNC);
$accepted = $statement->getResult($onRow, $onWarning, $onError, $onEnd, $onOk, $context);
// Для повторного UNIQUE-номера: $sent === true, $accepted === false.
// Подробности отказа получает $onError($context, $code, $sqlstate, $message).Что получается на исходных данных
| Наблюдение | Результат |
|---|---|
| Команда отправлена | да |
| Ответ сообщает успех | нет |
| Код отказа | 1062 |
| SQLSTATE | 23000 |
| Успешных callback завершения | 0 |
| Сохранённых заказов ORDER-A | 1 |
| Исходная сумма сохранена | 12.34 |
| Следующий запрос выполнен | да |
Самостоятельная программа и архив
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x145Config();
$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']];
function readOperation(mysql_xdevapi\SqlStatement $statement): array {
$context=(object)['events'=>[],'rows'=>[],'warnings'=>[],'errors'=>[],
'ends'=>[],'ok_calls'=>[]];
$onRow=static function ($ctx,array $row,array $columns) use ($context): void {
$context->events[]='row';
$context->rows[]=['same_context'=>$ctx===$context,'row'=>$row,
'column_types'=>array_map(static fn($column)=>get_debug_type($column),$columns)];
};
$onWarning=static function ($ctx,int $code,string $message) use ($context): void {
$context->events[]='warning';
$context->warnings[]=['same_context'=>$ctx===$context,'code'=>$code,
'message'=>x145SafeMessage($message)];
};
$onError=static function ($ctx,int $code,string $sqlstate,string $message) use ($context): void {
$context->events[]='error';
$context->errors[]=['same_context'=>$ctx===$context,'code'=>$code,
'sqlstate'=>$sqlstate,'message'=>x145SafeMessage($message)];
};
$onEnd=static function ($ctx,bool $hasMore) use ($context): void {
$context->events[]='resultset_end';
$context->ends[]=['same_context'=>$ctx===$context,'has_more'=>$hasMore];
};
$onOk=static function (...$args) use ($context): void {
$context->events[]='statement_ok';
$types=array_map(static fn($value)=>get_debug_type($value),$args);
$context->ok_calls[]=['argc'=>count($args),'types'=>$types,
'same_context'=>isset($args[0]) && $args[0]===$context,
'execution_status_received'=>in_array('mysql_xdevapi\ExecutionStatus',$types,true)];
};
$arguments=[$onRow,$onWarning,$onError,$onEnd,$onOk,$context];
$sent=$statement->execute(mysql_xdevapi\SqlStatement::EXECUTE_ASYNC);
if ($sent!==true) {throw new RuntimeException('Не удалось отправить команду.');}
$reads=[['method'=>'getResult','value'=>$statement->getResult(...$arguments)]];
$more=[$statement->hasMoreResults()];
while ($more[array_key_last($more)]) {
if (count($reads)>=4) {throw new RuntimeException('Ответ содержит больше четырёх ожидаемых частей.');}
$reads[]=['method'=>'getNextResult','value'=>$statement->getNextResult(...$arguments)];
$more[]=$statement->hasMoreResults();
}
return ['sent'=>$sent,'reads'=>$reads,'has_more'=>$more,'context'=>$context];
}
$name=x145OwnTable($session,$schema,$owned,'rejected_orders',
'(id INT AUTO_INCREMENT PRIMARY KEY, order_ref VARCHAR(30) NOT NULL UNIQUE, amount DECIMAL(8,2) NOT NULL) ENGINE=InnoDB');
$qualified=x145Qualified($schema,$name);
$session->sql('INSERT INTO '.$qualified.' (order_ref,amount) VALUES (?,?)')
->bind('ORDER-A')->bind('12.34')->execute();
$statement=$session->sql('INSERT INTO '.$qualified.' (order_ref,amount) VALUES (?,?)')
->bind('ORDER-A')->bind('99.99');
$outcome=readOperation($statement);
$context=$outcome['context'];
$error=$context->errors[0] ?? null;
if ($error===null) {throw new RuntimeException('Ожидаемый отказ повторной вставки не получен.');}
$saved=$session->sql('SELECT order_ref,amount FROM '.$qualified.' WHERE order_ref = ?')
->bind('ORDER-A')->execute()->fetchAll();
$control=$session->sql('SELECT 1 AS ready')->execute()->fetchOne();
$rows=[['Команда отправлена',$outcome['sent']],['Ответ сообщает успех',$outcome['reads'][0]['value']],
['Код отказа',$error['code']],['SQLSTATE',$error['sqlstate']],
['Успешных callback завершения',count($context->ok_calls)],
['Сохранённых заказов ORDER-A',count($saved)],['Исходная сумма сохранена',$saved[0]['amount']],
['Следующий запрос выполнен',$control['ready']===1]];
$checks=['sent_not_completed'=>$outcome['sent']===true && $outcome['reads']===[['method'=>'getResult','value'=>false]],
'duplicate_received'=>$context->events===['error'] && count($context->errors)===1 && $error['code']===1062
&& $error['sqlstate']==='23000' && $error['same_context']===true && $error['message']!=='',
'no_success_event'=>$context->ok_calls===[],
'original_unchanged'=>$saved===[['order_ref'=>'ORDER-A','amount'=>'12.34']],
'error_response_consumed'=>$outcome['has_more']===[false] && $control===['ready'=>1]];
$observations=['callback_outcome'=>$outcome,'saved_rows'=>$saved,'control'=>$control,
'attempted'=>['order_ref'=>'ORDER-A','amount'=>'99.99']];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x145Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x145Report('mysql145-x-callback-rejected-import', 'Отклонить повторный заказ и сохранить исходные данные', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Команда отправлена — да
Ответ сообщает успех — нет
Код отказа — 1062
SQLSTATE — 23000
Успешных callback завершения — 0
Сохранённых заказов ORDER-A — 1
Исходная сумма сохранена — 12.34
Следующий запрос выполнен — да
CLI-программа создаёт собственные учебные объекты в подготовленной локальной схеме, сохраняет результат и настоящую диагностику, затем удаляет только эти объекты. Это самостоятельный пример, не обработчик формы.
Таблица фактического запуска · Исходные данные и диагностика JSON.
Границы и типичные ошибки
Не интерпретируйте любое false как отсутствие заказов: в этом случае это отрицательный итог команды, а ошибка содержит причину. Для обычного execute отказ обрабатывается исключением; callback-путь здесь сохраняет его в onError и не создаёт успешного события. Не запускайте retry вслепую: совпавший номер может означать корректный повтор уже принятого запроса или другое содержимое под тем же ключом. Идемпотентный API сравнивает значимые поля и имеет явную политику конфликта. При повторе с 99.99 нельзя молча заменить 12.34. Эта одиночная команда работает в autocommit; для многошагового импорта нужен явный rollback/commit всей предметной транзакции. Один ответ дочитывается одним способом: нельзя одновременно перебирать callback-ответ и ожидать готовый SqlStatementResult. Контрольный SELECT подтверждает пригодность именно этого соединения после обработанного отказа.
Вопрос о MySQLi показывает проблему жизненного цикла: новая команда приходит на соединение, где ещё ожидается предыдущий ответ. Не переносим mysqli_poll или free_result в X DevAPI. Наш consumer дочитывает его собственный ответ, сохраняет отказ и только затем выполняет контрольный SELECT. Обсуждение: Следующий запрос отправлен раньше чтения асинхронного ответа.
Измените требование
Повторное сообщение содержит те же ORDER-A и 12.34. Можно ли вернуть существующий заказ?
Разбор следующего шага
После обработки нарушения UNIQUE прочитайте существующую строку и сравните все поля, которые определяют смысл запроса. При полном совпадении ваша политика может признать повтор идемпотентным и вернуть тот же заказ. При различии, как в показанных 99.99, верните понятный конфликт. Ограничение UNIQUE остаётся обязательным, а предварительный SELECT сам по себе не устраняет гонку двух клиентов.
Объекты и методы результата
| Инструмент | Задача |
|---|---|
| Warning::__construct | Получить объяснение преобразования данных из ответа сервера: объект Warning создаёт расширение, а не код через new. |
| SqlStatementResult::getGeneratedIds | Отличить сгенерированные документные _id от SQL AUTO_INCREMENT и не принять пустой список за провал вставки. |
| ExecutionStatus | Различить отправленную команду, подтверждённую операцию и доступную квитанцию, не приписывая профилю отсутствующий объект. |
| ExecutionStatus::__construct | Получать результат операции через рабочий публичный путь и понимать, почему new ExecutionStatus не создаёт подтверждение сервера. |
Подтвердить результат в своём сервисе
Выберите одну операцию своего сервиса и отдельно запишите: что подтверждает отправка, что подтверждает серверный ответ, какие предупреждения допустимы и какой ID связывает ответ с сохранённой записью. Затем определите поведение при повторном номере запроса. Создание документных команд, несколько ответов и обычная диагностика и связанные изменения в транзакции дают следующие шаги.
Наличие четырёх карточек и выполненных примеров не является заявлением о полноте всего расширения или PHP Manual. Ограничения конкретного нативного callback-контракта описаны рядом с полезной рабочей альтернативой.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
mysql_xdevapi\Warning::__constructПолучить объяснение преобразования данных из ответа сервера: объект Warning создаёт расширение, а не код через new.
ExecutionStatus: PECL mysql-xdevapi >= 8.0.11; страницы остальных выбранных методов не указывают начальную версию. Проверяемый профиль: 8.0.30. · mysql_xdevapi
private mysql_xdevapi\Warning::__construct()Получить объяснение преобразования данных из ответа сервера: объект Warning создаёт расширение, а не код через new.
Запись, параметры и результат
private mysql_xdevapi\Warning::__construct()Параметров нет. private закрывает прямое создание объекта прикладным кодом. Для получения предупреждений сохраните результат выполненной команды и вызовите getWarnings(); метод возвращает массив объектов, который может быть пустым.
Конструктор не служит публичной фабрикой и не имеет описанного возвращаемого значения. У реально полученного Warning доступны message, level и code; это диагностика операции, а не исключение, которое автоматически отменило запись.
Официальная страница не указывает начальную версию метода, содержит только закрытую сигнатуру без параметров и пустой пример. Это ограничение документации сохранено явно. В PECL 8.0.30 native create_warning копирует сообщение, уровень и код из серверного ответа; Reflection-обход конструктора не создаёт такой ответ.
Два применения
Показать оператору изменение цены
Оператор увидит причину расхождения цены. В полном файле получен настоящий Warning с кодом 1265 и уровнем 1.
$warnings = $priceReceipt->getWarnings();
$messages = [];
foreach ($warnings as $warning) {
$messages[] = ['code' => $warning->code, 'level' => $warning->level, 'message' => $warning->message];
}
// Вместе с messages покажите исходные 12.345 и прочитанные из базы 12.35.Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Сохранить предупреждения одной партии импорта
Предупреждения привязаны к конкретной партии и не исчезнут из отчёта после следующего SELECT. Пустой список остаётся допустимым результатом; не следует без проверки обращаться к элементу [0].
$batchReport = ['batch' => 'catalog-17', 'warnings' => []];
foreach ($importReceipt->getWarnings() as $warning) {
$batchReport['warnings'][] = ['code' => $warning->code, 'level' => $warning->level, 'message' => $warning->message];
}
$batchReport['warning_count'] = $importReceipt->getWarningsCount();Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Границы и связанные инструменты
Сначала получите диагностику нужной команды, затем выполняйте несвязанные запросы. Число предупреждений и число вставленных строк отвечают на разные вопросы. Не заменяйте Warning придуманным объектом с такими же свойствами. Callback onWarning этого профиля передаёт числовой code и строку message, но не level: для полноценного списка Warning используйте обычный результат. Сообщения могут содержать значения или внутренние имена; наружный ответ сервиса должен подчиняться его политике раскрытия данных.
Обычные квитанции, предупреждения и несколько ответов развивает устройство результата; Фабрики документов и момент execute объясняет создание команды. Идентификаторы, предупреждение об округлении и отказ повторной вставки показывают разные исходы.
mysql_xdevapi\SqlStatementResult::getGeneratedIdsОтличить сгенерированные документные _id от SQL AUTO_INCREMENT и не принять пустой список за провал вставки.
ExecutionStatus: PECL mysql-xdevapi >= 8.0.11; страницы остальных выбранных методов не указывают начальную версию. Проверяемый профиль: 8.0.30. · mysql_xdevapi
public mysql_xdevapi\SqlStatementResult::getGeneratedIds(): arrayОтличить сгенерированные документные _id от SQL AUTO_INCREMENT и не принять пустой список за провал вставки.
Запись, параметры и результат
public mysql_xdevapi\SqlStatementResult::getGeneratedIds(): arrayАргументов нет. Вызов относится к уже полученному SqlStatementResult конкретной операции; предварительно нужен execute(), а не просто созданный SqlStatement.
Массив сгенерированных _id последней операции этого результата; если их нет, возвращается []. Это не список всех затронутых первичных ключей и не замена getLastInsertId для SQL AUTO_INCREMENT.
PHP Manual указывает array и пустой массив при отсутствии generated _id, но остальной текст помечен как недокументированный, а пример оставлен пустым. Начальная версия на странице не указана. Matching PECL 8.0.30 возвращает generated_doc_ids из exec_state; при отсутствии внутреннего exec_state исходник выдаёт PHP-предупреждение. Для обычной SQL-вставки наш реальный ответ содержит [], одновременно с положительным getLastInsertId.
Два применения
Вернуть ссылку на созданный SQL-заказ
Пустой документный список не препятствует чтению ORDER-101 по его AUTO_INCREMENT. Полный файл показывает соответствие полученного ID и сохранённой строки.
$receipt = $insertOrder->execute();
$orderId = $receipt->getLastInsertId();
$generatedDocumentIds = $receipt->getGeneratedIds();
// По orderId читаем именно созданный заказ; generatedDocumentIds здесь равен [].Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Разделить каналы идентификаторов в отчёте интеграции
В отчёте явно разделены номер SQL-заказа, пустой список document IDs SQL-ответа и строковые IDs созданных документов. Код не смешивает два типа хранилища под расплывчатым полем id.
$sqlReply = $sqlInsert->execute();
$documentReply = $collection->add(['sku' => 'LED', 'qty' => 2])->execute();
$report = [
'order_id' => $sqlReply->getLastInsertId(),
'sql_generated_document_ids' => $sqlReply->getGeneratedIds(),
'product_document_ids' => $documentReply->getGeneratedIds(),
];Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Границы и связанные инструменты
Не вычисляйте ID через MAX(id), не требуйте последовательности без пропусков и не стройте список ключей массового INSERT арифметикой от первого ID без отдельного контракта сервера. Для явно заданного _id список сгенерированных значений может быть пустым. Не приводите автоматически строковый документный идентификатор к int. Последующий запрос выполняется по ID именно нужной квитанции.
Обычные квитанции, предупреждения и несколько ответов развивает устройство результата; Фабрики документов и момент execute объясняет создание команды. Идентификаторы, предупреждение об округлении и отказ повторной вставки показывают разные исходы.
mysql_xdevapi\ExecutionStatusРазличить отправленную команду, подтверждённую операцию и доступную квитанцию, не приписывая профилю отсутствующий объект.
ExecutionStatus: PECL mysql-xdevapi >= 8.0.11; страницы остальных выбранных методов не указывают начальную версию. Проверяемый профиль: 8.0.30. · mysql_xdevapi
class mysql_xdevapi\ExecutionStatus {
public $affectedItems;
public $matchedItems;
public $foundItems;
public $lastInsertId;
public $lastDocumentId;
private function __construct();
}Различить отправленную команду, подтверждённую операцию и доступную квитанцию, не приписывая профилю отсутствующий объект.
Запись, параметры и результат
class mysql_xdevapi\ExecutionStatus {
public $affectedItems;
public $matchedItems;
public $foundItems;
public $lastInsertId;
public $lastDocumentId;
private function __construct();
}Это класс служебного статуса выполнения, а не подключение, запрос или массив строк. Запись выше — справочная схема нативного класса, а не PHP-файл для объявления собственной копии. В официальном обзоре перечислены пять публичных свойств и закрытый конструктор. Объект должен исходить от расширения, а не создаваться приложением.
Официальный обзор не описывает фабрику и не даёт гарантий значений свойств. В matching исходнике native create_execution_status копирует счётчики и последний insert ID. В фактическом callback statement_ok PECL 8.0.30 передан только объект контекста; экземпляр ExecutionStatus в прикладной callback не поступил.
Официальная доступность класса: PECL mysql-xdevapi >= 8.0.11. Сохраняем все свойства, включая lastDocumentId, и private __construct. Пустое описание на PHP.net не дополняется вымышленным контрактом. Причина наблюдаемого ограничения: mysqlx_sql_statement.cc создаёт второй параметр ExecutionStatus, но задаёт число передаваемых параметров равным одному. Исходник getter lastDocumentId читает last_insert_id; поэтому это поле нельзя документировать как проверенный строковый ID документа.
| Свойство | Что видно в matching исходнике | Как применять знание |
|---|---|---|
| affectedItems | Исходник получает native affected-items count | Для рабочего счётчика обычного ответа используйте getAffectedItemsCount(). |
| matchedItems | Исходник получает native matched-items count | Не объявляем его равным числу изменённых значений или прочитанных строк. |
| foundItems | Исходник получает native found-items count | Не заменяет COUNT нужной выборки; фактического значения ExecutionStatus здесь нет. |
| lastInsertId | Исходник получает native last_insert_id | Обычный SQL-ответ предоставляет getLastInsertId(). |
| lastDocumentId | В этом исходнике читает тот же last_insert_id | Для документов нужен Result::getGeneratedIds(); строковый _id отсюда не обещается. |
Два применения
Сформировать подтверждение созданного заказа
Сервис получает настоящий счётчик и ID из SqlStatementResult. Для этой задачи не требуется создавать ExecutionStatus или зависеть от недоступного callback-аргумента.
$receipt = $insertOrder->execute(); // Обычный execute без ASYNC.
$confirmation = [
'inserted' => $receipt->getAffectedItemsCount(),
'order_id' => $receipt->getLastInsertId(),
];Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Зафиксировать фактический контракт callback при смене профиля
В проверенном профиле журнал содержит argc=1 и тип stdClass: это наш контекст. Доказательство не заменяется ожиданием второго аргумента; результат сохранённой суммы читается отдельно.
$onOk = static function (...$args) use ($log): void {
$log->ok_calls[] = [
'argc' => count($args),
'types' => array_map(static fn($value) => get_debug_type($value), $args),
];
};
// Весь callback-reader и отправка команды показаны в самостоятельном файле.Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Границы и связанные инструменты
Класс может существовать в расширении и одновременно не быть доступным через выбранную ветку callback. Не создавайте Reflection-экземпляр или DTO и не выдавайте его за серверную квитанцию. Не делайте из ограничения вывод, что запись невозможна: обычные Result/SqlStatementResult работают. Успешное событие не фиксирует открытую предметную транзакцию. Наличие свойства по имени не доказывает его семантику, значение или единицы.
Обычные квитанции, предупреждения и несколько ответов развивает устройство результата; Фабрики документов и момент execute объясняет создание команды. Идентификаторы, предупреждение об округлении и отказ повторной вставки показывают разные исходы.
mysql_xdevapi\ExecutionStatus::__constructПолучать результат операции через рабочий публичный путь и понимать, почему new ExecutionStatus не создаёт подтверждение сервера.
ExecutionStatus: PECL mysql-xdevapi >= 8.0.11; страницы остальных выбранных методов не указывают начальную версию. Проверяемый профиль: 8.0.30. · mysql_xdevapi
private mysql_xdevapi\ExecutionStatus::__construct()Получать результат операции через рабочий публичный путь и понимать, почему new ExecutionStatus не создаёт подтверждение сервера.
Запись, параметры и результат
private mysql_xdevapi\ExecutionStatus::__construct()Параметров нет. Закрытый конструктор нельзя вызывать из внешнего прикладного кода. Команда и её результат создаются методами расширения; операция должна действительно быть выполнена.
Официальная страница не описывает возврат и содержит только placeholder-пример. Вызов new из пользовательского кода запрещён видимостью. В этой версии native callback не передаёт созданный внутри ExecutionStatus, поэтому доступный результат получаем обычным execute.
На странице PHP Manual нет начальной версии метода; класс отдельно отмечен PECL >= 8.0.11. Сигнатура private без параметров сохранена. Наши примеры не обходят закрытость и не подделывают свойства: для подтверждения вставки используются реальные SqlStatementResult либо Result. Внутренняя create_execution_status и публичный конструктор — разные уровни API.
Два применения
Получить квитанцию SQL-платёжного документа
В учебном одиночном INSERT created равен 1, а invoiceId относится к фактически вставленной строке. Здесь нет отправки платежа: речь о локальной записи документа и её квитанции.
$receipt = $insertInvoice->execute();
$invoiceId = $receipt->getLastInsertId();
$created = $receipt->getAffectedItemsCount();
// При отказе обычного execute обработайте исключение, не создавайте фиктивный status.Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Показать отказ повторного импорта вместо успешной квитанции
Сервер возвращает 1062 и 23000, успешного callback нет, исходный ORDER-A остаётся с 12.34. Отсутствие успешного статуса является частью результата; его нельзя исправлять созданием new ExecutionStatus.
$outcome = readOperation($duplicateInsert);
if ($outcome['context']->errors !== []) {
$failure = $outcome['context']->errors[0];
$report = ['accepted' => false, 'code' => $failure['code'], 'sqlstate' => $failure['sqlstate']];
}
// readOperation полностью определена в самостоятельном файле.Контекстный фрагмент: переменные команд и результатов уже подготовлены. Полный самостоятельный пример с исходными данными.
Границы и связанные инструменты
Не используйте конструктор как форму отчёта: модель вашего API и нативная квитанция — разные объекты. Если приложению нужен собственный DTO, назовите его предметно и заполняйте только из проверенного ответа, явно отличая его от mysql_xdevapi\ExecutionStatus. Пример с callback нужен для объяснения границы API; для обычного синхронного обработчика проще доступный результат execute. Не завершайте скрипт сразу после ASYNC-send с обещанием, что незавершённая команда обязательно сохранится.
Обычные квитанции, предупреждения и несколько ответов развивает устройство результата; Фабрики документов и момент execute объясняет создание команды. Идентификаторы, предупреждение об округлении и отказ повторной вставки показывают разные исходы.
Источники и обсуждения
- PHP Manual — Warning::__construct
- PHP Manual — SqlStatementResult::getGeneratedIds
- PHP Manual — ExecutionStatus
- PHP Manual — ExecutionStatus::__construct
- MySQL 8.4 — диагностические условия и SHOW WARNINGS
- MySQL 8.4 — округление точных чисел
- PHP Manual — SqlStatement::getResult
- Matching исходник PECL mysql_xdevapi 8.0.30
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


