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

Дерево руководства
Глава 57 · После параметризованных SQLSRV-запросов и чтения результатов
SQLSRV: выходные параметры, точные значения и справочник констант
Счёт 179.00, резерв 3 → 7, Unicode-квитанция и точные числовые строки: рабочие OUT/INOUT-процедуры плюс все группы констант SQLSRV.
Сумма, остаток и квитанция: какой ответ нужен приложению
После вызова базы сайт часто должен показать две вещи: нужные строки и отдельный итог, остаток или квитанцию. Например, для счёта 71 — три позиции и 179.00 рубля; для резерва 3 единиц — остаток 7. В SQL Server такую операцию можно оформить хранимой процедурой. Это именованный блок SQL на сервере, который принимает параметры и выполняет заданные действия; здесь каждая программа сама создаёт свою учебную процедуру и данные.
Параметр — место для значения в объявлении процедуры. Аргумент — конкретное значение при вызове. Направление задаёт, откуда оно приходит: IN из PHP, OUT из базы, INOUT в обе стороны. Это похоже на бланк: в одно поле вы вписываете номер заказа, другое заполняет склад. Аналогия описывает роли полей, но не сетевой протокол: драйвер выдаёт выходные значения только после завершения результатов.
- PHP хранит invoiceId 71 и total 0
- CALL связывает ? с параметрами процедуры
- Процедура выполняет SQL только для invoiceId 71
- PHP читает строки SELECT и проверяет конец всех результатов
- OUT обновляет total, приложение показывает итог
| Что нужно странице | Какой контракт подходит |
|---|---|
| Отдельный итог для выбранного счёта | OUT:17900 копеек |
| Запрос резерва должен стать остатком | INOUT:3 → 7 |
| Короткий текст с кириллицей без потери символов | Размер OUT и UTF-8 |
| Точные цифры суммы/внешнего номера | Строковые DECIMAL и BIGINT |
| Найти имя режима и понять его ограничения | Полный сгруппированный каталог констант |
Эта глава продолжает подключение и типизированные параметры SQLSRV. Процедуры нужны не для каждого SELECT: простой параметризованный запрос зачастую понятнее. OUT удобно использовать при работе с уже существующим интерфейсом SQL Server или небольшим отдельным результатом операции. Для большого списка строк, документа, NULL или DateTime обычно выбирают набор результата SELECT и обычное чтение.
Разобрать вызов по элементам: ссылки, направления, PHP-тип и SQL-тип
Общая запись одного расширенного параметра: [значение или ссылка, направление, тип PHP, тип SQL]. Массив таких описаний передаётся третьим аргументом sqlsrv_query или sqlsrv_prepare. Порядок строго соответствует знакам ? слева направо. Последние элементы необязательны, но явный контракт помогает сохранить деньги, кириллицу и внешние номера.
$invoiceId = 71;
$total = 0;
$params = [
[$invoiceId, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_INT, SQLSRV_SQLTYPE_INT],
[&$total, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_INT, SQLSRV_SQLTYPE_INT],
];
// $proc — известное имя процедуры, созданной полным примером ниже.
$stmt = sqlsrv_query($db, "{CALL $proc(?,?)}", $params);| Фрагмент | Что он означает |
|---|---|
| $total=0 | Инициализация PHP-переменной до привязки. Не инициализируйте OUT значением null. |
| &$total | Драйвер должен изменить ту же переменную. Сама переменная должна жить до завершения команды. |
| SQLSRV_PARAM_OUT | Направление параметра; отдельно от OUTPUT в SQL-объявлении. |
| SQLSRV_PHPTYPE_INT | PHP получит int, а не строку, поток или float. |
| SQLSRV_SQLTYPE_INT | Тип соответствующего SQL-параметра; тип PHP и тип SQL — два разных решения. |
| {CALL $proc(?,?)} | Каноническая запись вызова процедуры. ? привязывает значение; имя $proc не может быть получено из формы. |
| statement / false | Возврат query/prepare — дескриптор команды либо ошибка; OUT не является возвращаемым значением функции PHP. |
Процедура может одновременно выдавать строки SELECT, сообщения о числе изменений и выходные значения. Текущий первый результат уже активен после выполнения. Сначала прочитайте его, затем переходите к следующему. true от next_result означает следующий результат, null — завершение, false — ошибку. Не используйте while(next_result(...)) без проверки false: так отказ будет похож на нормальный конец. При повторном sqlsrv_execute явно задавайте новые входные значения; не теряйте ссылки, заменяя связанный массив без подготовки новой команды.
OUT-параметр также отличается от SQL RETURN: RETURN в хранимой процедуре — отдельный целочисленный код завершения. Результат SELECT — набор строк. OUTPUT у UPDATE inserted.qty — способ получить изменённые значения строк. Эти механизмы не взаимозаменяемы; ниже они подписаны по своей роли.
Получить строки счёта и его итог через OUT
Страница счёта показывает три позиции и сумму 179.00 рубля. Процедура принимает номер 71, возвращает нужные строки и отдельно записывает итог в PHP-переменную. Соседний счёт 72 не загружается. Это полезно, когда существующий SQL-интерфейс уже отдаёт итог через OUTPUT и его нужно правильно подключить к сайту.
- PHP: invoiceId=71 и переменная total=0
- Первый ? → @invoiceId; второй ? → @total OUTPUT
- SQL выбирает только строки счёта 71 и считает SUM на сервере
- PHP читает текущие строки, затем next_result до null
- В total теперь 17900; интерфейс показывает 179.00
Сначала небольшой вызов
$total = 0;
$params = [71, [&$total, SQLSRV_PARAM_OUT,
SQLSRV_PHPTYPE_INT, SQLSRV_SQLTYPE_INT]];
$stmt = sqlsrv_query($db, "{CALL $proc(?,?)}", $params);
// Прочитать все результаты и проверить ошибки; затем использовать $total.
// В полном примере: $total === 17900.OUT — канал из процедуры в вызывающий PHP-код. & перед $total сохраняет ссылку на ту переменную, которую драйвер обновит. Знак ? — место значения, а не имя столбца. Две позиции массива соответствуют двум ? в том же порядке. Вложенный массив задаёт значение/ссылку, направление, тип PHP и тип SQL. SQL OUTPUT в объявлении процедуры и SQLSRV_PARAM_OUT в PHP нужны на разных сторонах. Функция sqlsrv_query возвращает statement либо false; сам итог приходит через $total. Переменная заранее инициализирована числом. finishContract152 показана в полном коде: она читает текущий результат, отличает false от null и не теряет первый набор строк.
Что получится
| Наблюдение | Результат |
|---|---|
| Счёт | 71 |
| Получено строк | 3 |
| Номера строк | 1, 2, 3 |
| Итог, копеек | 17900 |
| К оплате, рублей | 179.00 |
| PHP-тип итога | int |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s152Runtime();
try {
$config=s152Config();$db=s152Connect($config);$runtime=s152Runtime($db,$config);
function finishContract152($stmt): array {
$sets=[];
for ($result=0;$result<16;$result++) {
$fields=sqlsrv_num_fields($stmt);
if ($fields===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($fields>0) {
$rows=[];
while (true) {
$row=sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC);
if ($row===null) {break;}
if ($row===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
$rows[]=$row;
}
$sets[]=$rows;
}
$next=sqlsrv_next_result($stmt);
if ($next===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($next===null) {return ['done'=>true,'sets'=>$sets,'diagnostics'=>[]];}
}
throw new RuntimeException('Учебная процедура неожиданно вернула больше 16 результатов.');
}
$name=s152OwnTable($db,$owned,'invoice','(invoice_id int NOT NULL,line_no int NOT NULL,qty int NOT NULL,unit_kopecks int NOT NULL,PRIMARY KEY(invoice_id,line_no))');
$q=s152Qualified($name);
s152Command($db,"INSERT INTO $q(invoice_id,line_no,qty,unit_kopecks) VALUES(71,1,1,10000),(71,2,2,2700),(71,3,1,2500),(72,1,9,900)");
$procName=s152OwnProcedure($db,$owned,'invoice_total',"@invoiceId int,@total int OUTPUT AS BEGIN SET NOCOUNT ON;
SELECT line_no,qty,unit_kopecks,qty*unit_kopecks AS line_total FROM $q WHERE invoice_id=@invoiceId ORDER BY line_no;
SELECT @total=COALESCE(SUM(qty*unit_kopecks),0) FROM $q WHERE invoice_id=@invoiceId; END");
$proc=s152Qualified($procName);$invoiceId=71;$total=0;
$params=[[$invoiceId,SQLSRV_PARAM_IN,SQLSRV_PHPTYPE_INT,SQLSRV_SQLTYPE_INT],
[&$total,SQLSRV_PARAM_OUT,SQLSRV_PHPTYPE_INT,SQLSRV_SQLTYPE_INT]];
$stmt=s152Query($db,"{CALL $proc(?,?)}",$params);
try {$result=finishContract152($stmt);} finally {sqlsrv_free_stmt($stmt);}
if (!$result['done']) {throw new RuntimeException('Не все результаты счёта прочитаны.');}
$lines=$result['sets'][0]??[];
$amount=intdiv($total,100).'.'.str_pad((string)($total%100),2,'0',STR_PAD_LEFT);
$rows=[['Счёт',$invoiceId],['Получено строк',count($lines)],['Номера строк',implode(', ',array_column($lines,'line_no'))],['Итог, копеек',$total],['К оплате, рублей',$amount],['PHP-тип итога',get_debug_type($total)]];
$checks=['all_results_finished'=>$result['done'],'three_invoice_lines'=>count($lines)===3,
'correct_line_order'=>array_column($lines,'line_no')===[1,2,3],
'line_totals'=>array_column($lines,'line_total')===[10000,5400,2500],
'one_result_set'=>count($result['sets'])===1,'output_exact'=>$total===17900,'visible_amount'=>$amount==='179.00'];
$observations=['invoice_lines'=>$lines,'output_total'=>$total,'completion'=>$result];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-param-output','Получить строки счёта и его итог через OUT',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Счёт — 71
Получено строк — 3
Номера строк — 1, 2, 3
Итог, копеек — 17900
К оплате, рублей — 179.00
PHP-тип итога — int
Самостоятельная программа включает lab.php и README.txt. Она создаёт свою небольшую таблицу и процедуру SQL Server, вызывает процедуру с параметрами, сохраняет result.html/result.json/metrics.json и удаляет только собственные учебные объекты. Процедура создаётся одним SQL-вызовом без GO: GO — команда внешнего клиента, а не часть отправляемого T-SQL.
Фактический результат программы · Данные результата JSON.
Ограничения и ошибки
OUT нельзя считать готовым до получения всех результатов процедуры: отдельный SELECT может прийти раньше. SET NOCOUNT ON убирает лишние сообщения о количестве изменённых строк, но не убирает наборы SELECT. Здесь массив PHP ограничен тремя позициями одного счёта; не используйте этот учебный сборщик для выгрузки всей таблицы. int подходит только в пределах его диапазона; для крупных сумм используйте точный DECIMAL из последнего сценария. Пустой счёт в этом контракте даёт 0; если это ошибка предметной области, добавьте отдельную проверку наличия счёта.
Процедура возвращала строки и выходной флаг, но приложение получало непонятное значение и пыталось читать строки после перехода к последнему результату. Исходная ситуация на Stack Overflow. В нашем сценарии сначала читаются три нужные строки счёта, затем подтверждается завершение всех результатов, и только после этого используется переменная OUT. Направление, PHP-тип и ссылка заданы явно.
Измените условие задачи
Запросите счёт 72. Какие строки и итог должны появиться без фильтрации массива PHP?
Как проверить решение
Процедура вернёт одну строку с qty=9 и unit_kopecks=900, итог 8100 копеек. Меняется только входной параметр; WHERE и SUM остаются на сервере.
Передать количество резерва и получить остаток через INOUT
На складе 10 единиц кабеля, заказу нужны 3. После операции сайт должен показать «зарезервировано 3, осталось 7». В INOUT одна привязанная переменная сначала несёт запрос 3, затем принимает ответ 7. Сохраним исходное количество отдельно, чтобы квитанция не перепутала запрос и остаток.
- amount=3: желаемый резерв
- INOUT передаёт 3 в SQL-параметр @amount
- Один UPDATE уменьшает qty 10 → 7, только если хватает остатка
- OUTPUT inserted.qty фиксирует результат именно этого UPDATE
- После всех результатов amount=7; requested остаётся 3
Сначала небольшой вызов
$amount = 3;
$requested = $amount;
$params = [1, [&$amount, SQLSRV_PARAM_INOUT,
SQLSRV_PHPTYPE_INT, SQLSRV_SQLTYPE_INT]];
// После успешного вызова и чтения всех результатов:
// $requested === 3; $amount === 7.IN передаёт значение только в SQL; OUT получает его из SQL; INOUT делает оба действия. OUTPUT в SQL-объявлении используется и для OUT, и для INOUT; клиент задаёт направление отдельно. @requested — локальная SQL-переменная, а @changed — небольшая табличная переменная внутри процедуры, не новая постоянная таблица. SQL OUTPUT inserted.qty берёт значение непосредственно изменённой строки. Это другой смысл слова OUTPUT, чем у выходного параметра: сначала UPDATE кладёт остаток в @changed, затем SELECT присваивает его @amount. Условие достаточного остатка находится в самом UPDATE.
Что получится
| Наблюдение | Результат |
|---|---|
| Товар | Кабель-3x2.5 |
| Запрошено к резерву | 3 |
| INOUT после завершения | 7 |
| Остаток в таблице | 7 |
| Результат процедуры | зарезервировано 3, осталось 7 |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s152Runtime();
try {
$config=s152Config();$db=s152Connect($config);$runtime=s152Runtime($db,$config);
function finishContract152($stmt): array {
$sets=[];
for ($result=0;$result<16;$result++) {
$fields=sqlsrv_num_fields($stmt);
if ($fields===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($fields>0) {
$rows=[];
while (true) {
$row=sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC);
if ($row===null) {break;}
if ($row===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
$rows[]=$row;
}
$sets[]=$rows;
}
$next=sqlsrv_next_result($stmt);
if ($next===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($next===null) {return ['done'=>true,'sets'=>$sets,'diagnostics'=>[]];}
}
throw new RuntimeException('Учебная процедура неожиданно вернула больше 16 результатов.');
}
$name=s152OwnTable($db,$owned,'stock','(id int NOT NULL PRIMARY KEY,sku nvarchar(40) NOT NULL,qty int NOT NULL CHECK(qty>=0))');
$q=s152Qualified($name);
s152Command($db,"INSERT INTO $q(id,sku,qty) VALUES(1,N'Кабель-3x2.5',10)");
$procName=s152OwnProcedure($db,$owned,'reserve',"@id int,@amount int OUTPUT AS BEGIN SET NOCOUNT ON;
DECLARE @requested int=@amount; DECLARE @changed TABLE(qty int NOT NULL);
UPDATE $q SET qty=qty-@requested OUTPUT inserted.qty INTO @changed(qty)
WHERE id=@id AND @requested>0 AND qty>=@requested;
IF @@ROWCOUNT<>1 THROW 50001,N'Недостаточно остатка или неверный запрос',1;
SELECT @amount=qty FROM @changed;
SELECT @requested AS reserved,@amount AS remaining; END");
$proc=s152Qualified($procName);$productId=1;$amount=3;$requested=$amount;
$params=[[$productId,SQLSRV_PARAM_IN,SQLSRV_PHPTYPE_INT,SQLSRV_SQLTYPE_INT],
[&$amount,SQLSRV_PARAM_INOUT,SQLSRV_PHPTYPE_INT,SQLSRV_SQLTYPE_INT]];
$stmt=s152Query($db,"{CALL $proc(?,?)}",$params);
try {$result=finishContract152($stmt);} finally {sqlsrv_free_stmt($stmt);}
if (!$result['done']) {throw new RuntimeException('Резерв не завершён.');}
$saved=s152Read($db,"SELECT sku,qty FROM $q WHERE id=?",[$productId])[0];
$rows=[['Товар',$saved['sku']],['Запрошено к резерву',$requested],['INOUT после завершения',$amount],['Остаток в таблице',$saved['qty']],['Результат процедуры','зарезервировано 3, осталось 7']];
$checks=['all_results_finished'=>$result['done'],'input_preserved_for_receipt'=>$requested===3,
'inout_now_remaining'=>$amount===7,'database_updated_once'=>$saved['qty']===7,
'receipt_matches'=>$result['sets']===[[['reserved'=>3,'remaining'=>7]]]];
$observations=['requested'=>$requested,'remaining'=>$amount,'saved'=>$saved,'results'=>$result['sets']];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-param-inout','Передать количество резерва и получить остаток через INOUT',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Товар — Кабель-3x2.5
Запрошено к резерву — 3
INOUT после завершения — 7
Остаток в таблице — 7
Результат процедуры — зарезервировано 3, осталось 7
Самостоятельная программа включает lab.php и README.txt. Она создаёт свою небольшую таблицу и процедуру SQL Server, вызывает процедуру с параметрами, сохраняет result.html/result.json/metrics.json и удаляет только собственные учебные объекты. Процедура создаётся одним SQL-вызовом без GO: GO — команда внешнего клиента, а не часть отправляемого T-SQL.
Фактический результат программы · Данные результата JSON.
Ограничения и ошибки
Нельзя сначала прочитать остаток в PHP, посчитать и затем без условия записать его обратно: параллельный заказ может изменить строку между этими действиями. Здесь один UPDATE проверяет и уменьшает запас атомарно; несколько связанных операций потребуют транзакции. При недостатке товара процедура делает THROW, а PHP проверяет как начальный вызов, так и последующие результаты. После ошибки OUT/INOUT не является подтверждением успеха. Повторный вызов с уже изменённой amount=7 запросит 7, поэтому перед повтором задавайте новое входное значение намеренно.
Измените условие задачи
Перед повторным выполнением явно присвойте amount=2. Каким должен стать остаток после первого успешного резерва 3?
Как проверить решение
В таблице после первого вызова 7. Второй запрос 2 должен вернуть 5. Для безопасного повторения сетевого запроса дополнительно нужен идентификатор операции и защита от повторного списания; INOUT сам по себе этого не даёт.
Вернуть кириллицу полностью и распознать короткий буфер
После приёмки товара нужно показать «Получено: Кабель №7». Сначала получим эту строку целиком в UTF-8. Затем намеренно зададим клиентскую вместимость 4 вместо 80 и увидим управляемую ошибку обрезания. Сайт должен сообщить об отказе, а не выдавать обрезанный или ещё не завершённый OUT за готовую квитанцию.
- SQL: label=N'Кабель №7', выходной параметр nvarchar(80)
- Правильная привязка: PHP string UTF-8 + SQL NVARCHAR(80)
- Дочитать текущий результат и завершение → полная квитанция
- Повторная привязка NVARCHAR(4) → SQLSTATE 01004
- Диагностика сохраняется сразу; ошибочный OUT не используется
Сначала небольшой вызов
$receipt = '';
$binding = [&$receipt, SQLSRV_PARAM_OUT,
SQLSRV_PHPTYPE_STRING('UTF-8'), SQLSRV_SQLTYPE_NVARCHAR('80')];
// После успешного завершения: 'Получено: Кабель №7'.Кодировка PHP-строки и вместимость SQL-параметра — разные настройки. PHPTYPE_STRING('UTF-8') определяет PHP-представление. SQLTYPE_NVARCHAR('80') передаёт размер при привязке; в проверенном драйвере аргумент size имеет тип string, поэтому при strict_types используются кавычки. NVARCHAR(n) измеряется парами байтов UTF-16, а strlen PHP измеряет байты UTF-8; это не взаимозаменяемые числа. Кириллица этого примера занимает по одной UTF-16 единице на знак; символы вне BMP могут требовать две. finishContract152 проверяет не только sqlsrv_query и next_result, но и fetch_array: обрезание в проверенном драйвере проявилось при чтении строки.
Что получится
| Наблюдение | Результат |
|---|---|
| Полная квитанция | Получено: Кабель №7 |
| PHP-тип квитанции | string |
| Вместимость правильного параметра | NVARCHAR(80) |
| Слишком короткий параметр | NVARCHAR(4) |
| Обрезание распознано по SQLSTATE | 01004 |
| Значение ошибочного OUT | не используется |
| Исходная этикетка в базе | Кабель №7 |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s152Runtime();
try {
$config=s152Config();$db=s152Connect($config);$runtime=s152Runtime($db,$config);
function finishContract152($stmt): array {
$sets=[];
for ($result=0;$result<16;$result++) {
$fields=sqlsrv_num_fields($stmt);
if ($fields===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($fields>0) {
$rows=[];
while (true) {
$row=sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC);
if ($row===null) {break;}
if ($row===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
$rows[]=$row;
}
$sets[]=$rows;
}
$next=sqlsrv_next_result($stmt);
if ($next===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($next===null) {return ['done'=>true,'sets'=>$sets,'diagnostics'=>[]];}
}
throw new RuntimeException('Учебная процедура неожиданно вернула больше 16 результатов.');
}
$name=s152OwnTable($db,$owned,'labels','(id int NOT NULL PRIMARY KEY,label nvarchar(40) NOT NULL)');
$q=s152Qualified($name);
s152Command($db,"INSERT INTO $q(id,label) VALUES(1,?)",[
['Кабель №7',SQLSRV_PARAM_IN,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_NVARCHAR('40')]]);
$procName=s152OwnProcedure($db,$owned,'label_receipt',"@id int,@receipt nvarchar(80) OUTPUT AS BEGIN SET NOCOUNT ON;
SELECT @receipt=N'Получено: '+label FROM $q WHERE id=@id;
IF @@ROWCOUNT<>1 THROW 50003,N'Этикетка не найдена',1;
SELECT @id AS receipt_id; END");
$proc=s152Qualified($procName);$receipt='';
$params=[1,[&$receipt,SQLSRV_PARAM_OUT,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_NVARCHAR('80')]];
$stmt=s152Query($db,"{CALL $proc(?,?)}",$params);
try {$valid=finishContract152($stmt);} finally {sqlsrv_free_stmt($stmt);}
if (!$valid['done']) {throw new RuntimeException('Полная квитанция не получена.');}
// Deliberate local failure: the SQL parameter remains80; only the client binding is too short.
$oldWarnings=sqlsrv_get_config('WarningsReturnAsErrors');
if (!sqlsrv_configure('WarningsReturnAsErrors',true)) {s152Failure('Настройка учебной диагностики');}
$short='';$bad=null;
try {
$shortParams=[1,[&$short,SQLSRV_PARAM_OUT,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_NVARCHAR('4')]];
$badStmt=sqlsrv_query($db,"{CALL $proc(?,?)}",$shortParams,['QueryTimeout'=>10]);
if ($badStmt===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
$bad=['done'=>false,'sets'=>[],'diagnostics'=>s152Diagnostics($errors)];
} else {
try {$bad=finishContract152($badStmt);} finally {sqlsrv_free_stmt($badStmt);}
}
// Do not display or trust $short: output buffers after failure are not a valid receipt.
} finally {
if (!sqlsrv_configure('WarningsReturnAsErrors',$oldWarnings)) {s152Failure('Восстановление настройки диагностики');}
}
$truncated=false;
foreach ($bad['diagnostics'] as $diagnostic) {
if ($diagnostic['SQLSTATE']==='01004') {$truncated=true;}
}
$saved=s152Read($db,"SELECT label FROM $q WHERE id=?",[1])[0]['label'];
$rows=[['Полная квитанция',$receipt],['PHP-тип квитанции',get_debug_type($receipt)],['Вместимость правильного параметра','NVARCHAR(80)'],['Слишком короткий параметр','NVARCHAR(4)'],['Обрезание распознано по SQLSTATE','01004'],['Значение ошибочного OUT','не используется'],['Исходная этикетка в базе',$saved]];
$checks=['valid_finished'=>$valid['done'],'unicode_complete'=>$receipt==='Получено: Кабель №7',
'valid_string'=>is_string($receipt),'small_buffer_failed'=>$bad['done']===false,
'truncation_diagnostic'=>$truncated,'original_label_intact'=>$saved==='Кабель №7',
'warning_policy_restored'=>sqlsrv_get_config('WarningsReturnAsErrors')===$oldWarnings];
$observations=['complete_receipt'=>$receipt,'valid_results'=>$valid['sets'],
'too_small'=>['done'=>$bad['done'],'diagnostics'=>$bad['diagnostics']],
'original_label'=>$saved,'small_output_omitted'=>'Недостоверный OUT после ошибки не читается в отчёт.'];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-param-text-buffer','Вернуть кириллицу полностью и распознать короткий буфер',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Полная квитанция — Получено: Кабель №7
PHP-тип квитанции — string
Вместимость правильного параметра — NVARCHAR(80)
Слишком короткий параметр — NVARCHAR(4)
Обрезание распознано по SQLSTATE — 01004
Значение ошибочного OUT — не используется
Исходная этикетка в базе — Кабель №7
Самостоятельная программа включает lab.php и README.txt. Она создаёт свою небольшую таблицу и процедуру SQL Server, вызывает процедуру с параметрами, сохраняет result.html/result.json/metrics.json и удаляет только собственные учебные объекты. Процедура создаётся одним SQL-вызовом без GO: GO — команда внешнего клиента, а не часть отправляемого T-SQL.
Фактический результат программы · Данные результата JSON.
Ограничения и ошибки
Для этого управляемого отказа WarningsReturnAsErrors временно включён и затем восстановлен. SQLSTATE 01004 сообщает о потере части данных; его нельзя заглушить настройкой и считать текст полным. Не увеличивайте всё автоматически до MAX: потоковые типы не поддерживаются как OUT. Большой документ возвращайте SELECT и читайте отдельно. Если сама процедура уже сузила данные до NVARCHAR(4), клиентский размер 80 исходный текст не восстановит. Веб-вывод квитанции всё равно требует HTML-экранирования; кодировка не заменяет его.
При возврате большого XML через OUT разработчик столкнулся с обрезанием длинного документа. Исходная ситуация на Stack Overflow. Размер результата и способ его передачи — часть интерфейса процедуры. Здесь короткая квитанция имеет явную вместимость NVARCHAR(80); большой XML или файл лучше вернуть SELECT и читать как результат/поток. Исторический порог из обсуждения не объявляется универсальным лимитом текущего драйвера.
Слишком короткий параметр самой SQL-процедуры незаметно обрезал входную строку ещё до последующей обработки. Исходная ситуация на Stack Overflow. Проверять обе стороны: размер SQL-параметра и контракт привязки PHP. Увеличение клиентского буфера не восстановит уже обрезанный сервером текст. Для отказа от слишком длинного ввода проверка должна видеть исходное значение до сужения типа.
Измените условие задачи
Увеличьте только размер клиентской привязки с 4 до 80, оставив серверную процедуру прежней. Какая проверка теперь подтверждает исправление?
Как проверить решение
Нужны завершение всех результатов и строгое совпадение всей строки «Получено: Кабель №7», а не просто отсутствие исключения. Сравнение strlen с 80 бессмысленно: это разные единицы размера.
Передать точную сумму и большой номер без округления
При сверке документов нужно передать точную сумму с четырьмя знаками после точки и внешний номер 9223372036854775806. Здесь цифры важнее возможности немедленно сложить значения оператором + в PHP. Получим оба OUT-параметра строками и сохраним этот контракт в JSON: кавычки защитят номер от округления у получателя с ограниченной точностью чисел.
- SQL хранит total в DECIMAL(19,4), external_id в BIGINT
- PHP задаёт строковый выходной тип для обоих параметров
- Процедура выбирает одну запись по id
- После завершения total и externalId остаются точными строками
- JSON сохраняет цифры и четыре десятичных знака
Сначала небольшой вызов
$total = '0';
$binding = [&$total, SQLSRV_PARAM_OUT,
SQLSRV_PHPTYPE_STRING('UTF-8'), SQLSRV_SQLTYPE_DECIMAL(19, 4)];
// После вызова: '123456789012345.6789', без преобразования во float.DECIMAL(19,4) означает 19 десятичных цифр всего, из них 4 после точки. SQLSRV_PHPTYPE_STRING управляет представлением в PHP; SQLSRV_SQLTYPE_DECIMAL описывает серверную сторону. Для BIGINT серверный тип указывается отдельно, даже если PHP получает строку. На 64-битном PHP выбранный BIGINT помещается в int, но передача строкой также сохраняет его в JSON-потребителях с меньшей точностью чисел. В полном коде проверяется не только тип, но и совпадение всех цифр с независимым чтением таблицы.
Что получится
| Наблюдение | Результат |
|---|---|
| Сумма DECIMAL(19,4) | 123456789012345.6789 |
| PHP-тип суммы | string |
| Номер BIGINT | 9223372036854775806 |
| PHP-тип номера | string |
| JSON для передачи | {"total":"123456789012345.6789","external_id":"9223372036854775806"} |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s152Runtime();
try {
$config=s152Config();$db=s152Connect($config);$runtime=s152Runtime($db,$config);
function finishContract152($stmt): array {
$sets=[];
for ($result=0;$result<16;$result++) {
$fields=sqlsrv_num_fields($stmt);
if ($fields===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($fields>0) {
$rows=[];
while (true) {
$row=sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC);
if ($row===null) {break;}
if ($row===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
$rows[]=$row;
}
$sets[]=$rows;
}
$next=sqlsrv_next_result($stmt);
if ($next===false) {
$errors=sqlsrv_errors(SQLSRV_ERR_ALL);
return ['done'=>false,'sets'=>$sets,'diagnostics'=>s152Diagnostics($errors)];
}
if ($next===null) {return ['done'=>true,'sets'=>$sets,'diagnostics'=>[]];}
}
throw new RuntimeException('Учебная процедура неожиданно вернула больше 16 результатов.');
}
$name=s152OwnTable($db,$owned,'exact','(id int NOT NULL PRIMARY KEY,total decimal(19,4) NOT NULL,external_id bigint NOT NULL)');
$q=s152Qualified($name);
$inputTotal='123456789012345.6789';$inputId='9223372036854775806';
s152Command($db,"INSERT INTO $q(id,total,external_id) VALUES(1,?,?)",[
[$inputTotal,SQLSRV_PARAM_IN,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_DECIMAL(19,4)],
[$inputId,SQLSRV_PARAM_IN,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_BIGINT]]);
$procName=s152OwnProcedure($db,$owned,'exact_receipt',"@id int,@total decimal(19,4) OUTPUT,@externalId bigint OUTPUT AS BEGIN SET NOCOUNT ON;
SELECT @total=total,@externalId=external_id FROM $q WHERE id=@id;
IF @@ROWCOUNT<>1 THROW 50002,N'Запись не найдена',1;
SELECT @id AS receipt_id; END");
$proc=s152Qualified($procName);$total='0';$externalId='0';
$params=[1,[&$total,SQLSRV_PARAM_OUT,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_DECIMAL(19,4)],
[&$externalId,SQLSRV_PARAM_OUT,SQLSRV_PHPTYPE_STRING('UTF-8'),SQLSRV_SQLTYPE_BIGINT]];
$stmt=s152Query($db,"{CALL $proc(?,?,?)}",$params);
try {$result=finishContract152($stmt);} finally {sqlsrv_free_stmt($stmt);}
if (!$result['done']) {throw new RuntimeException('Точные выходные значения не получены.');}
$payload=json_encode(['total'=>$total,'external_id'=>$externalId],JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);
$decoded=json_decode($payload,true,512,JSON_THROW_ON_ERROR);
$saved=s152Read($db,"SELECT total,external_id FROM $q WHERE id=?",[1])[0];
$rows=[['Сумма DECIMAL(19,4)',$total],['PHP-тип суммы',get_debug_type($total)],['Номер BIGINT',$externalId],['PHP-тип номера',get_debug_type($externalId)],['JSON для передачи',$payload]];
$checks=['all_results_finished'=>$result['done'],'decimal_exact'=>$total===$inputTotal,
'bigint_exact'=>$externalId===$inputId,'both_strings'=>is_string($total)&&is_string($externalId),
'json_preserves_contract'=>$decoded===['total'=>$inputTotal,'external_id'=>$inputId],
'readback_agrees'=>$saved===['total'=>$inputTotal,'external_id'=>$inputId],
'receipt_selected'=>$result['sets']===[[['receipt_id'=>1]]]];
$observations=['output'=>['total'=>$total,'external_id'=>$externalId],'database'=>$saved,'json'=>$payload];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-param-exact-output','Передать точную сумму и большой номер без округления',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Сумма DECIMAL(19,4) — 123456789012345.6789
PHP-тип суммы — string
Номер BIGINT — 9223372036854775806
PHP-тип номера — string
JSON для передачи — {"total":"123456789012345.6789","external_id":"9223372036854775806"}
Самостоятельная программа включает lab.php и README.txt. Она создаёт свою небольшую таблицу и процедуру SQL Server, вызывает процедуру с параметрами, сохраняет result.html/result.json/metrics.json и удаляет только собственные учебные объекты. Процедура создаётся одним SQL-вызовом без GO: GO — команда внешнего клиента, а не часть отправляемого T-SQL.
Фактический результат программы · Данные результата JSON.
Ограничения и ошибки
Строковый формат не превращает + в точную десятичную арифметику. Для расчётов используйте SQL DECIMAL либо подходящую десятичную библиотеку PHP; не приводите сумму к float и не применяйте JSON_NUMERIC_CHECK к этому контракту. Не меняйте масштаб 4 на 2 без принятого правила округления. OUT-параметры с NULL, DateTime или потоком не поддерживаются данным контрактом драйвера: для таких данных возвращайте строку результата SELECT и выбирайте подходящий способ чтения.
Измените условие задачи
Измените входную сумму на 123.4000. Что нужно сохранить при получении и передаче?
Как проверить решение
При неизменном DECIMAL(19,4) ожидается строка 123.4000, а не float123.4. Проверяйте полное строковое значение и наличие кавычек в JSON; формат для отображения пользователю можно построить отдельно.
Константы SQLSRV по назначению и функции описания типов
Константа — готовое имя настройки, заданное расширением. SQLSRV_PARAM_OUT читается как намерение «получить значение», а не как случайное число 4. Эти имена доступны только при загруженном sqlsrv. Здесь сохранены все группы официальной страницы констант и отмечены расхождения её написаний с проверенным SQLSRV 5.13.3. Это справочник имён и выбора; наличие имени не означает, что любой тип разрешён во всех направлениях параметра.
- Фильтровать строку по id → значение IN
- Получить итог в переменную → PARAM_OUT + PHPTYPE + SQLTYPE
- Прочитать строку SELECT → FETCH и при необходимости SCROLL
- Разобрать отказ → ERR, настройки журнала → LOG
- Выбрать изоляцию соединения → TXN
Не все элементы этого справочника — обычные константы. SQLSRV_SQLTYPE_NVARCHAR без скобок — код типа для сравнения, а SQLSRV_SQLTYPE_NVARCHAR('80') — функция, формирующая описание с размером для привязки. SQLSRV_PHPTYPE_STRING и SQLSRV_PHPTYPE_STREAM в 5.13.3 являются функциями, а не одноимёнными обычными константами. Поэтому defined() и function_exists() отвечают на разные вопросы.
// Короткие диагностические фрагменты, без соединения с базой.
var_dump(defined('SQLSRV_PARAM_OUT')); // true при загруженном sqlsrv
var_dump(function_exists('SQLSRV_PHPTYPE_STRING')); // true
$utf8 = SQLSRV_PHPTYPE_STRING('UTF-8');
$size80 = SQLSRV_SQLTYPE_NVARCHAR('80');
// Для сравнения metadata['Type']: SQLSRV_SQLTYPE_NVARCHAR без ('80').Направление параметра
| Имя | Для чего выбирается |
|---|---|
SQLSRV_PARAM_IN | PHP → SQL; например, номер выбранного счёта. |
SQLSRV_PARAM_OUT | SQL → привязанная переменная PHP; итог после завершения всех результатов. |
SQLSRV_PARAM_INOUT | Вход и выход в одной переменной; запрос резерва становится остатком. |
Представление значения в PHP
| Имя | Для чего выбирается |
|---|---|
SQLSRV_PHPTYPE_INT | Целое число в диапазоне PHP int. |
SQLSRV_PHPTYPE_FLOAT | Число с плавающей точкой; не контракт точных денежных цифр. |
SQLSRV_PHPTYPE_DATETIME | Объект DateTime при чтении; не поддерживаемый OUT-параметр. |
SQLSRV_PHPTYPE_NULL | Маркер PHP NULL, присутствует в 5.13.3. Не означает разрешение использовать NULL как OUT. |
SQLSRV_PHPTYPE_TABLE | Маркер табличного параметра TVP в современных версиях; требует отдельного SQL-типа и структуры данных, не произвольного PHP-массива. |
Кодировка строк и потоков
| Имя | Для чего выбирается |
|---|---|
SQLSRV_ENC_BINARY | Байты без текстового преобразования: содержимое файла. |
SQLSRV_ENC_CHAR | Системная кодовая страница; для переносимой кириллицы задайте UTF-8 явно. |
Вид массива строки результата
| Имя | Для чего выбирается |
|---|---|
SQLSRV_FETCH_ASSOC | Ключи — названия/AS-псевдонимы столбцов. |
SQLSRV_FETCH_NUMERIC | Ключи 0,1,… по порядку SELECT. |
SQLSRV_FETCH_BOTH | Оба набора ключей; режим по умолчанию у fetch_array, не две строки результата. |
Диагностика последнего вызова
| Имя | Для чего выбирается |
|---|---|
SQLSRV_ERR_ALL | Ошибки и предупреждения; сохраните сразу после неудачного вызова. |
SQLSRV_ERR_ERRORS | Только ошибки. |
SQLSRV_ERR_WARNINGS | Только предупреждения, например обрезание данных. |
Nullable в метаданных столбца
| Имя | Для чего выбирается |
|---|---|
SQLSRV_NULLABLE_NO | Столбец не допускает NULL. В 5.13.3 значение 0. |
SQLSRV_NULLABLE_YES | Столбец допускает NULL. В 5.13.3 значение 1; это не означает, что текущая строка содержит NULL. |
SQLSRV_NULLABLE_UNKNOWN | Драйвер не определил допустимость; значение 2, не разрешение записывать NULL. |
Подсистемы журнала: LogSubsystems
| Имя | Для чего выбирается |
|---|---|
SQLSRV_LOG_SYSTEM_OFF | Отключить журнал подсистем. |
SQLSRV_LOG_SYSTEM_INIT | Инициализация расширения. |
SQLSRV_LOG_SYSTEM_CONN | Операции подключения. |
SQLSRV_LOG_SYSTEM_STMT | Работа statement. |
SQLSRV_LOG_SYSTEM_UTIL | Служебная обработка ошибок и предупреждений. |
SQLSRV_LOG_SYSTEM_ALL | Все подсистемы; диагностический режим, а не обязательная настройка сайта. |
Уровни журнала: LogSeverity
| Имя | Для чего выбирается |
|---|---|
SQLSRV_LOG_SEVERITY_ERROR | Записывать ошибки. |
SQLSRV_LOG_SEVERITY_WARNING | Записывать предупреждения. |
SQLSRV_LOG_SEVERITY_NOTICE | Записывать уведомления. |
SQLSRV_LOG_SEVERITY_ALL | Все три уровня. Отбор журнала не меняет успешность самого SQL-запроса. |
Изоляция соединения: TransactionIsolation
| Имя | Для чего выбирается |
|---|---|
SQLSRV_TXN_READ_UNCOMMITTED | Допускает чтение неподтверждённых изменений; не универсальное средство ускорения. |
SQLSRV_TXN_READ_COMMITTED | Не читать неподтверждённые изменения; поведение зависит также от настройки версионирования базы. |
SQLSRV_TXN_REPEATABLE_READ | Повторное чтение уже прочитанных строк в транзакции защищено от их изменения; фантомы ещё возможны. |
SQLSRV_TXN_SERIALIZABLE | Защищает также прочитанный диапазон; возможны дополнительные блокировки. |
SQLSRV_TXN_SNAPSHOT | Снимок для транзакции; требуется разрешение snapshot в базе и обработка конфликтов записи. |
Тип курсора: опция Scrollable
| Имя | Для чего выбирается |
|---|---|
SQLSRV_CURSOR_FORWARD | Движение вперёд; обычное последовательное чтение без полного клиентского буфера. |
SQLSRV_CURSOR_STATIC | Прокрутка по снимку результата; подходит для ограниченного набора. |
SQLSRV_CURSOR_DYNAMIC | Курсор отражает изменения данных; доступность операций отличается от STATIC. |
SQLSRV_CURSOR_KEYSET | Состав ключей фиксируется, значения существующих строк могут меняться. |
SQLSRV_CURSOR_CLIENT_BUFFERED | Результат буферизуется на клиенте; занимает память, ограничен ClientBufferMaxKBSize. |
Позиция строки при прокрутке
| Имя | Для чего выбирается |
|---|---|
SQLSRV_SCROLL_NEXT | Следующая строка; доступно и при последовательном чтении. |
SQLSRV_SCROLL_PRIOR | Предыдущая строка прокручиваемого курсора. |
SQLSRV_SCROLL_FIRST | Первая строка. |
SQLSRV_SCROLL_LAST | Последняя строка. |
SQLSRV_SCROLL_ABSOLUTE | Позиция от начала; offset0 означает первую строку, если курсор поддерживает операцию. |
SQLSRV_SCROLL_RELATIVE | Смещение от текущей позиции, например offset=-1 назад. |
SQL-типы: константы для описания и сравнения
| Имя | Для чего выбирается |
|---|---|
SQLSRV_SQLTYPE_BIGINT | Большое целое: внешние номера; точный строковый OUT разобран выше. |
SQLSRV_SQLTYPE_BIT | Логический признак в SQL:0/1 и, если разрешено, NULL. |
SQLSRV_SQLTYPE_TINYINT | Небольшое неотрицательное целое. |
SQLSRV_SQLTYPE_SMALLINT | Целое малого диапазона. |
SQLSRV_SQLTYPE_INT | Обычный SQL int, например количество или id учебной записи. |
SQLSRV_SQLTYPE_DECIMAL | Точные десятичные данные; для привязки используйте DECIMAL(p,s). |
SQLSRV_SQLTYPE_NUMERIC | Синоним decimal; для привязки используйте NUMERIC(p,s). |
SQLSRV_SQLTYPE_FLOAT | Приближённое число двойной точности. |
SQLSRV_SQLTYPE_REAL | Приближённое число меньшей точности. |
SQLSRV_SQLTYPE_MONEY | Денежный SQL-тип с четырьмя десятичными знаками. |
SQLSRV_SQLTYPE_SMALLMONEY | Денежный SQL-тип меньшего диапазона. |
SQLSRV_SQLTYPE_CHAR | Строка фиксированной длины; при привязке CHAR(n). |
SQLSRV_SQLTYPE_VARCHAR | Строка переменной длины; при привязке VARCHAR(n) или VARCHAR('max'). |
SQLSRV_SQLTYPE_NCHAR | Unicode фиксированной длины; при привязке NCHAR(n). |
SQLSRV_SQLTYPE_NVARCHAR | Unicode переменной длины; при привязке NVARCHAR(n) или NVARCHAR('max'). |
SQLSRV_SQLTYPE_TEXT | Старый текстовый тип; для нового хранения предпочтителен varchar(max). |
SQLSRV_SQLTYPE_NTEXT | Старый Unicode-тип; для нового хранения предпочтителен nvarchar(max). |
SQLSRV_SQLTYPE_BINARY | Фиксированные байты; при привязке BINARY(n). |
SQLSRV_SQLTYPE_VARBINARY | Переменные байты; при привязке VARBINARY(n) или VARBINARY('max'). |
SQLSRV_SQLTYPE_IMAGE | Старый бинарный тип; современная альтернатива varbinary(max), имя не ограничивает данные картинками. |
SQLSRV_SQLTYPE_DATE | Календарная дата без времени. |
SQLSRV_SQLTYPE_TIME | Время суток. |
SQLSRV_SQLTYPE_DATETIME | Старый тип даты и времени со своей точностью и диапазоном. |
SQLSRV_SQLTYPE_SMALLDATETIME | Дата и время с точностью до минуты. |
SQLSRV_SQLTYPE_DATETIME2 | Дата и время с расширенными точностью/диапазоном. |
SQLSRV_SQLTYPE_DATETIMEOFFSET | Дата и время с числовым смещением часового пояса. |
SQLSRV_SQLTYPE_TIMESTAMP | Бинарный rowversion для контроля изменения строки, не дата/время. |
SQLSRV_SQLTYPE_UNIQUEIDENTIFIER | SQL GUID; не обычный числовой id. |
SQLSRV_SQLTYPE_XML | XML-значение SQL Server; большой документ удобнее вернуть набором SELECT. |
SQLSRV_SQLTYPE_UDT | Пользовательский SQL-тип: контракт зависит от созданного на сервере типа. |
SQLSRV_SQLTYPE_TABLE | Табличный параметр TVP, присутствует в 5.13.3; это не имя обычной таблицы и не возможность OUT-массива. |
Функции, собирающие описание типа
При корректных аргументах эти вызовы возвращают целочисленное описание типа для драйвера, а не значение из таблицы. size в проверенной сборке 5.13.3 передаётся строкой; precision и scale — целыми. Используйте имена и вызовы, а не сохраняйте внутренние числовые коды в конфигурацию. Недопустимое описание или неверный PHP-тип аргумента — ошибка привязки; при strict_types передача числа 80 вместо строки'80' в размерный конструктор вызывает TypeError до SQL-запроса.
| Сигнатура | Пример | Параметры и границы |
|---|---|---|
SQLSRV_PHPTYPE_STRING($encoding) | SQLSRV_PHPTYPE_STRING('UTF-8') | Строка PHP. Кодировка обязательна: UTF-8, SQLSRV_ENC_CHAR либо SQLSRV_ENC_BINARY. |
SQLSRV_PHPTYPE_STREAM($encoding) | SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY) | Поток PHP для файла/большого столбца. Это не поддерживаемый OUT-параметр. |
SQLSRV_SQLTYPE_BINARY($size) | SQLSRV_SQLTYPE_BINARY('16') | 1…8000 байт; фиксированный размер, без max. |
SQLSRV_SQLTYPE_VARBINARY($size) | SQLSRV_SQLTYPE_VARBINARY('max') | 1…8000 байт либо строка max для большого бинарного значения. |
SQLSRV_SQLTYPE_CHAR($size) | SQLSRV_SQLTYPE_CHAR('12') | 1…8000 байт; число отображаемых символов зависит от кодировки. |
SQLSRV_SQLTYPE_VARCHAR($size) | SQLSRV_SQLTYPE_VARCHAR('80') | 1…8000 байт либо max; UTF-8 символ может занимать несколько байт. |
SQLSRV_SQLTYPE_NCHAR($size) | SQLSRV_SQLTYPE_NCHAR('12') | 1…4000 пар байтов UTF-16; фиксированная длина. |
SQLSRV_SQLTYPE_NVARCHAR($size) | SQLSRV_SQLTYPE_NVARCHAR('80') | 1…4000 пар байтов UTF-16 либо max. Это не число байтов строки PHP. |
SQLSRV_SQLTYPE_DECIMAL($precision,$scale) | SQLSRV_SQLTYPE_DECIMAL(19,4) | precision 1…38; scale 0…precision. Четыре знака дробной части в этом примере. |
SQLSRV_SQLTYPE_NUMERIC($precision,$scale) | SQLSRV_SQLTYPE_NUMERIC(12,0) | Те же правила точности/масштаба; scale 0 допустим. |
Значение max разрешено для VARCHAR, NVARCHAR и VARBINARY, но не делает их поддерживаемыми OUT-параметрами. Для файлов используйте поток входного параметра и чтение потока столбца. Строка 'UTF-8' — имя кодировки, а не отсутствующая константа SQLSRV_ENC_UTF8. Наличие PHPTYPE_TABLE/SQLTYPE_TABLE обозначает отдельный интерфейс TVP; подробный запуск табличных параметров здесь не подменяется передачей произвольного массива.
Уточнения официального списка для установленного драйвера
| Что можно встретить в документации | Что использовать и почему |
|---|---|
| SQLSRV_TXN_READ_SERIALIZABLE в PHP Manual | В 5.13.3 существует SQLSRV_TXN_SERIALIZABLE. Вариант с READ отсутствует; не копируйте его в код. |
| SQLSRV_CURSOR_BUFFERED в PHP Manual | В 5.13.3 существует SQLSRV_CURSOR_CLIENT_BUFFERED; короткое имя отсутствует. |
| Курсоры и ENC помечены как int | В 5.13.3 это строковые значения. Сравнивайте с именем константы и не навязывайте ей int. |
| Числа NULLABLE_YES/NO в таблице Microsoft | Исходник и работающий драйвер: NO=0, YES=1, UNKNOWN=2. Имена сохраняют смысл; сравнение с константой надёжнее переписанного числа. |
| scale начинается с 1 в таблице типов Microsoft | Для decimal/numeric допустимо 0…precision. DECIMAL(19,0) также проверен в сборке 5.13.3. |
| PHPTYPE_NULL, PHPTYPE_TABLE и SQLTYPE_TABLE отсутствуют в старом списке PHP Manual | Эти три имени есть в проверенной сборке. Здесь они сохранены отдельно с назначением и ограничениями. |
Список сверён с 76 зарегистрированными константами и 10 функциями описания типов установленного SQLSRV 5.13.3; это не утверждение, что все методы расширения уже разобраны отдельными учебными сценариями. Результаты наличия имён получены из самого расширения, спорные написания дополнительно сопоставлены с регистрацией символов Microsoft 5.13.3. На другой версии сначала проверьте расширение, а затем свойство/режим, который нужен задаче.
Практические продолжения: выбор строк, курсоры и next_result; настройки, журнал и память клиентского буфера; границы транзакций. Не увеличивайте ClientBufferMaxKBSize ради SELECT* всей большой таблицы: состав столбцов, WHERE, сортировка и ограничение числа строк должны соответствовать экрану ещё в SQL.
От выходного параметра к устойчивой операции сайта
Теперь у вызова процедуры есть проверяемый ответ: сумма совпадает со строками счёта, остаток — с таблицей, квитанция сохранена целиком, точные цифры не прошли через float. Значение OUT имеет смысл только вместе с успешным завершением результатов; «statement создан» ещё не подтверждает готовность ответа.
Дальше соедините операцию резерва с транзакцией для нескольких изменений и тайм-аутом и отменой команды. Если программа не видит константы или на сайте ведёт себя иначе, чем в консоли, перейдите к проверке PHP, расширения, настроек и ресурсов. Для документа большого размера используйте передачу и чтение потоком.
Источники и обсуждения
- PHP Manual — константы SQLSRV
- Microsoft — группы констант
- Microsoft — выходные параметры
- Microsoft — двунаправленные параметры
- Microsoft — направление параметров
- Microsoft — SQL-типы параметров
- Microsoft — регистрация символов SQLSRV 5.13.3
- Microsoft — точность и масштаб decimal/numeric
- Microsoft — размер nchar/nvarchar
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


