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

Дерево руководства
Глава 56 · После основ PHP; чтение SQL-примеров опирается на первую выборку, параметры и finally
SQLSRV: установить подходящий драйвер и понять настройки PHP
Паспорт PHP и загрузки расширения, требования и установка по платформам, настройки ini/драйвера, клиентский буфер под экран и владение ресурсами. Шесть разделов официальной структуры и четыре самостоятельных опыта.
Какие компоненты нужны и что проверяет эта глава
Чтобы PHP читал SQL Server, мало установить «что-нибудь для SQL». Нужно выбрать совместимые компоненты, загрузить расширение именно в тот PHP, который обслуживает приложение, и понимать срок жизни настроек и ресурсов. В этой главе сначала получается полезный паспорт среды, затем снимок настроек, небольшая страница в ограниченном буфере и доказательство независимости двух соединений.
- Ваш PHP-код вызывает sqlsrv_*
- Выбранный PHP SAPI загружает расширение SQLSRV
- Расширение использует Microsoft ODBC Driver
- ODBC общается с SQL Server / Azure SQL
- Ресурс statement отдаёт строки в массивы PHP
Стрелки показывают роли компонентов, а не пять разных серверов. PHP и ODBC обычно стоят на машине приложения; база может быть отдельно. PDO_SQLSRV — другой PHP API поверх того же семейства ODBC, а не обязательная замена sqlsrv_*. Если приложение использует только один API, второй не нужен для самого факта работы первого.
Маршрут: требования → установка → настройки → ресурсы. Отдельно доступен указатель всех 26 функций SQLSRV.
Четыре программы ниже проверяются на отдельном Windows CLI-стенде. Первые две не обращаются к базе; последние две используют собственные временные учебные таблицы. Команды установки Linux/macOS и веб-настройки объяснены по первичным документам, но не объявляются проверенными этими CLI-запусками.
Совместимость PHP, расширения, ODBC и сервера
Сначала определите версию PHP, разрядность и TS/NTS именно целевого процесса. Затем выбирайте выпуск Microsoft Drivers for PHP, поддерживающий эту версию, вашу ОС, ODBC и сервер. Буквы TS означают потокобезопасную сборку, NTS — сборку без этого режима; они не взаимозаменяемы. DLL должна совпадать с PHP, а не с названием установленной Windows.
| Компонент | Что проверить | Типичный признак несовпадения |
|---|---|---|
| PHP | Минорная версия, SAPI, TS/NTS, архитектура | CLI и сайт ведут себя по-разному. |
| SQLSRV или PDO_SQLSRV | Версия PHP и режим сборки из имени/описания пакета | Startup warning, undefined function или отсутствующий PDO-драйвер. |
| Microsoft ODBC Driver | Поддерживаемый выпуск и архитектура | Расширение загрузилось, но подключение не удалось из-за ODBC. |
| Зависимости ОС | Поддерживаемая ОС, нужные библиотеки времени выполнения; для сборки — инструменты и заголовки | DLL/.so существует, но её зависимость не загружается. |
| SQL Server / Azure SQL | Поддерживаемый сервер, сетевой адрес, аутентификация и права | Ошибка подключения либо функции сервера после успешной загрузки PHP-модуля. |
Проверенный профиль примеров: PHP 8.3.35 NTS x64, SQLSRV 5.13.3+20807, ODBC 18.07.0001, SQL Server 2022 LocalDB 16.00.1000. Это конкретный набор, не обязательная версия для всех читателей. Текущая матрица 5.13 включает PHP 8.3–8.5; выбор других ОС и серверов проверяйте по матрице Microsoft. Размер целого 64 бита сам по себе не доказывает совпадение x64 и ARM64.
Как читать старую страницу требований PHP Manual
В официальном узле sqlsrv.requirements сохранён исторический перечень Windows Vista SP2+, Windows Server 2008 SP2+, Server 2008 R2 и Windows 7, требование SQL Server 2012 Native Client и описание комплекта из восьми файлов, половина для PDO. Это контекст старых выпусков; не инструкция устанавливать Native Client 2012 или выбирать устаревшую Windows для SQLSRV 5.13. Современный стек сверяется с документами Microsoft и использует совместимый Microsoft ODBC Driver.
Загрузить расширение в нужный PHP, а не просто скопировать файл
Установка кладёт расширение на диск, загрузка включает его в конкретном PHP-процессе. Для Windows ZIP-варианта сначала выберите правильную DLL по версии PHP, TS/NTS и архитектуре, установите соответствующие зависимости/ODBC, поместите DLL в extension_dir и добавьте одну правильную строку extension в реально используемый ini. После изменения перезапустите нужный PHP-процесс или его пул; новый CLI-процесс прочитает конфигурацию при запуске.
; Пример именно для PHP 8.3 NTS x64 из ZIP-пакета:
extension=php_sqlsrv_83_nts_x64.dll
; Только если приложение использует PDO_SQLSRV:
; extension=php_pdo_sqlsrv_83_nts_x64.dllИмя файла выше не универсально. Для TS, другой минорной версии или архитектуры берётся другой файл, существующий в выбранном дистрибутиве. Не включайте одновременно TS и NTS либо x86 и x64 «на всякий случай». PDO должен быть загружен раньше PDO_SQLSRV, если он не встроен. Дополнительные ini тоже могут содержать extension: уберите дубли одной и той же загрузки.
php --ini
php -m
php -iЭто команды терминала, не PHP-код страницы. Они показывают конфигурацию выбранного CLI; запускайте нужный исполняемый файл по полному пути, если установлено несколько PHP. Для веб-процесса отдельно установите его версию, Loaded Configuration File, дополнительные ini и extension_dir. Временную диагностику открывают только локально или администратору и удаляют после работы; не публикуйте phpinfo с окружением. Успех CLI не подтверждает IIS/FastCGI, Apache или PHP-FPM.
Linux и macOS: тот же принцип, другие пакеты
На Linux нужны совместимые PHP и его development-пакет, Microsoft ODBC Driver, unixODBC с заголовками и инструменты сборки. В актуальном руководстве Microsoft основной путь для расширений — PIE; инструкции различаются для Ubuntu, Debian, Red Hat, SUSE и Alpine. На macOS дополнительно важно совпадение архитектур Homebrew, PHP и ODBC; не смешивайте arm64 и x86_64 через Rosetta незаметно.
# После подготовки выбранной ОС и проверенной установки PIE:
pie install microsoft/sqlsrv
# Если нужен PDO API, отдельная установка:
pie install microsoft/pdo_sqlsrvЭти команды приведены как этап платформенной установки, а не как полный автоматический установщик. PIE нужно получить и проверить по официальному руководству Linux/macOS; для нескольких PHP укажите правильный php-config. После сборки проверьте загрузку в CLI и целевом FPM/Apache отдельно, перезапустите соответствующие процессы. Конкретные пути ini и имена сервисов берутся из установки, их нельзя угадывать по другой машине. Ручная запись extension=sqlsrv.so нужна только если установщик не включил модуль; не добавляйте дубликат.
Страница sqlsrv.installation PHP Manual также содержит фразу о SQLSRV 4.0 и PHP 7.0 на Linux/Windows. Это версия-специфическое историческое замечание, не ограничение современных драйверов. Текущая установка Windows через ZIP или PIE, а также Linux/macOS сверена с инструкцией загрузки Microsoft. Здесь эти платформенные действия не выполняются автоматически.
Переходите к проверке соединения только после загрузки API. Для удалённой базы отдельно настройте TLS и проверяемый сертификат. Encrypt=false в архиве ограничен частным LocalDB через локальный named pipe; это не настройка для удалённого сайта и не исправление любой ошибки сертификата. Не превращайте TrustServerCertificate в универсальную замену исправлению доверия и имени сервера.
Параметры ini, драйвера, соединения и команды
Есть настройки старта PHP, текущей работы драйвера, соединения и отдельной команды. Они похожи на общий профиль приложения, параметры конкретного входа и параметры одного задания. Сначала определите область действия; иначе изменение «не того» параметра ничего не исправит.
| Настройка | Документированный стандарт | Что означает |
|---|---|---|
| sqlsrv.WarningsReturnAsErrors | 1; INI_ALL; с SQLSRV 1.0 | Политика предупреждений, а не подавление настоящих SQL-ошибок. |
| sqlsrv.LogSubsystems | 0; INI_ALL; с SQLSRV 1.0 | Какие части драйвера журналировать; 0 выключает журнал. |
| sqlsrv.LogSeverity | 1; INI_ALL; с SQLSRV 1.0 | Какие уровни записывать при включённом журнале. |
| sqlsrv.ClientBufferMaxKBSize | 10240 КБ в документации Microsoft | Положительный бюджет клиентского курсора; также задаётся configure или опцией команды. |
Первая тройка сохраняет базовую таблицу PHP Manual; настройка клиентского буфера добавлена по документации Microsoft. INI_ALL означает возможность менять настройку на разрешённых уровнях конфигурации, а не одинаковый php.ini у всех SAPI. Фактическое значение процесса читают, не угадывают по стандарту. Настройки sqlsrv_configure действуют в текущей работе и не записывают файл конфигурации.
Таблица выше сохраняет значения из базового PHP Manual. В проверенном SQLSRV 5.13.3 значение LogSeverity до изменений фактически было 0, а не указанное там 1. Поэтому приложение сначала читает свою среду, а finally возвращает именно прочитанное значение. sqlsrv_configure с LogSeverity = 0 в этой версии отказывает, хотя ini_set для sqlsrv.LogSeverity восстанавливает строку 0 и рабочее значение 0. В программе настроек журнал вообще не переключается: исходное значение остаётся нетронутым.
| Маска | Значения |
|---|---|
| LogSubsystems | INIT = 1, CONN = 2, STMT = 4, UTIL = 8; ALL = -1, OFF = 0. |
| LogSeverity | ERROR = 1, WARNING = 2, NOTICE = 4; ALL = -1. |
В PHP объединяйте нужные флаги оператором |, например SQLSRV_LOG_SYSTEM_CONN | SQLSRV_LOG_SYSTEM_STMT. Одной LogSeverity недостаточно, пока подсистемы выключены. Журнал драйвера и настройка PHP error_log — разные части маршрута; доступ к файлу, его место и срок хранения настраивают отдельно. Не показывайте полный текст диагностики посетителю: он может содержать пути и детали сервера.
| Область | Примеры |
|---|---|
| Текущие настройки драйвера | WarningsReturnAsErrors, LogSubsystems, LogSeverity, ClientBufferMaxKBSize. |
| Соединение | Database, CharacterSet, LoginTimeout, Encrypt, MultipleActiveResultSets. |
| Команда | QueryTimeout, Scrollable, ClientBufferMaxKBSize, SendStreamParamsAtExec. |
LoginTimeout ограничивает вход, QueryTimeout — SQL-команду. ClientBufferMaxKBSize касается клиентского буфера, memory_limit — общей памяти PHP. Ни один из них не превращает SELECT * с сотнями тысяч строк и сортировкой в PHP в подходящий запрос для короткого списка на экране. Практическая замена показана в опыте с бюджетом буфера.
Два ресурса драйвера и обычная строка данных PHP
Официальный раздел называет два вида ресурсов. Connection возвращает sqlsrv_connect и используется для отправки команд; statement возвращает sqlsrv_query или sqlsrv_prepare и используется для выполнения/чтения конкретной команды. Они не взаимозаменяемы и не являются строками подключения. Уже прочитанный массив данных — обычное значение PHP со своим сроком жизни.
- Код верхнего уровня владеет $db = connect(...)
- Команда получает $stmt = query($db,...)
- Читатель получает $row = fetch_array($stmt)
- free_stmt завершает $stmt; $row продолжает существовать
- close завершает $db после всех его пользователей
Передавайте существующее соединение функции, которой оно нужно, и определите, кто его закрывает. Присваивание в другую переменную не создаёт независимую сессию. Не храните resource в JSON/сессии как возможность подключиться позже; для нового процесса нужен новый вход. Для незавершённой транзакции сначала решите commit или rollback, затем освобождайте ресурсы. Полная программа показывает эти границы без вызовов на уже освобождённом дескрипторе.
Составить паспорт PHP перед выбором расширения
После установки DLL код всё ещё пишет «undefined function». Прежде чем переустанавливать базу, выясним, какой PHP выполняет файл и видит ли он нужный API. Отчёт покажет версию, SAPI, TS/NTS, разрядность, загрузку расширения и следующий шаг. Сервер базы для этого не нужен.
- Запускается выбранный php.exe, а не абстрактный «PHP на компьютере»
- PHP_VERSION + PHP_ZTS + PHP_INT_SIZE → версия / TS или NTS / разрядность
- extension_loaded + function_exists → доступен ли SQLSRV API
- php_ini_loaded_file + php_ini_scanned_files → откуда взята конфигурация
- Решение: исправить загрузку либо переходить к проверке подключения
Сначала короткий вызов
echo PHP_VERSION, "
";
echo PHP_SAPI, "
";
echo extension_loaded('sqlsrv') ? "SQLSRV загружен
" : "SQLSRV отсутствует
";PHP_VERSION и PHP_SAPI — готовые значения текущего процесса. extension_loaded получает имя расширения без имени DLL. В конструкции условие ? вариант_да : вариант_нет выбирается текст для echo. Наличие sqlsrv_connect проверяется function_exists до её вызова. SQLSRV и PDO_SQLSRV — разные расширения: установленный PDO-драйвер сам по себе не создаёт функции sqlsrv_*.
Результат лабораторного запуска
| Наблюдение | Результат |
|---|---|
| Версия PHP | 8.3.35 |
| Режим выполнения SAPI | cli |
| Режим потокобезопасности | NTS |
| Разрядность PHP | 64 |
| Расширение SQLSRV загружено | да |
| Три нужные функции доступны | да |
| Основной php.ini прочитан | нет |
| Дополнительных ini-файлов | 0 |
| Следующий шаг | SQLSRV API доступен; следующий шаг — подключение |
Полная программа и архив для самостоятельного запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s152Runtime();
try {
$loaded=extension_loaded('sqlsrv');
$functions=['sqlsrv_connect','sqlsrv_query','sqlsrv_close'];
$available=[];
foreach ($functions as $name) {$available[$name]=function_exists($name);}
$apiReady=$loaded && !in_array(false,$available,true);
$ini=php_ini_loaded_file();
$scanned=php_ini_scanned_files();
$iniFiles=($scanned===false || trim($scanned)==='')?[]:array_map('trim',explode(',',$scanned));
$baseName=static fn(string $path):string=>basename(str_replace(chr(92),'/',$path));
$thread=PHP_ZTS?'TS':'NTS';
$bits=PHP_INT_SIZE*8;
$decision=$apiReady?'SQLSRV API доступен; следующий шаг — подключение':'Сначала загрузите SQLSRV в этот PHP';
$rows=[['Версия PHP',PHP_VERSION],['Режим выполнения SAPI',PHP_SAPI],['Режим потокобезопасности',$thread],
['Разрядность PHP',$bits],['Расширение SQLSRV загружено',$loaded],['Три нужные функции доступны',$apiReady],
['Основной php.ini прочитан',$ini!==false],['Дополнительных ini-файлов',count($iniFiles)],['Следующий шаг',$decision]];
$checks=['version_present'=>PHP_VERSION!=='','sapi_present'=>PHP_SAPI!=='','known_word_size'=>in_array($bits,[32,64],true),
'decision_consistent'=>$apiReady===($loaded && count(array_filter($available))===3),
'no_database_connection'=>$db===null && $owned===[]];
$observations=['php'=>PHP_VERSION,'sapi'=>PHP_SAPI,'thread_safety'=>$thread,'word_size_bits'=>$bits,
'sqlsrv_extension_version'=>phpversion('sqlsrv')?:null,'function_availability'=>$available,
'loaded_ini_basename'=>$ini===false?null:$baseName($ini),'additional_ini_basenames'=>array_map($baseName,$iniFiles),
'extension_dir_basename'=>$baseName((string)ini_get('extension_dir')),'decision'=>$decision,
'scope'=>'This process only; no SQL connection attempted; CLI result does not certify a web SAPI.'];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-runtime-fingerprint','Составить паспорт PHP перед выбором расширения',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Версия PHP — 8.3.35
Режим выполнения SAPI — cli
Режим потокобезопасности — NTS
Разрядность PHP — 64
Расширение SQLSRV загружено — да
Три нужные функции доступны — да
Основной php.ini прочитан — нет
Дополнительных ini-файлов — 0
Следующий шаг — SQLSRV API доступен; следующий шаг — подключение
Запустите отдельный CLI пример по README.txt. К базе программа не подключается; исследуются только текущий PHP и загруженный драйвер. lab.php сохраняет result.html, result.json и metrics.json.
Открыть сохранённый результат · Снимки и проверки JSON.
Границы и ошибки
Здесь сохранён результат отдельного Windows CLI PHP 8.3.35 NTS x64. Запуск лаборатории использует -n и явные -d, поэтому отсутствие прочитанного php.ini ожидаемо, хотя расширение загружено. Это не образец обязательного отключения ini на сайте. В другой среде значения будут другими. PHP_INT_SIZE показывает ширину целого, а не различает все процессорные архитектуры: для ARM64/x64 дополнительно проверяйте исполняемый файл и пакет. Паспорт не доказывает наличие ODBC, доступ к серверу или работоспособность IIS/FPM. Полные пути смотрите локально командой php --ini; публичный отчёт сохраняет только имена файлов.
SQLSRV работал из браузера, но CLI не находил sqlsrv_connect. В обсуждении предположили разные ini; сам автор обнаружил, что загрузил только PDO_SQLSRV. Исходный вопрос на Stack Overflow. Проверяем оба независимых условия: какой runtime/ini запускается и какой именно API загружен. Не превращаем возможную разницу конфигураций в утверждение «всегда ровно два php.ini».
DLL существовали, но расширения не загружались: автор перемешивал TS/NTS и выбрал драйверы для другой минорной версии PHP. Исходный вопрос на Stack Overflow. Вместо случайного набора DLL составляем паспорт PHP и выбираем одно согласованное расширение для нужного API. Исторические имена PHP 7 из обсуждения не предлагаются для современной установки.
Измените одно требование
На сайте функция не найдена, но CLI сообщает «SQLSRV загружен». Что сравнить первым?
Как проверить изменение
Сравнить SAPI, версию/сборку PHP и реально прочитанные ini в CLI и веб-процессе. Проверить, загружен ли именно sqlsrv, а не только pdo_sqlsrv. Не менять пароль SQL Server, пока PHP ещё не умеет вызвать функцию подключения.
Прочитать настройки, изменить их в запросе и восстановить
Диагностический участок временно меняет поведение драйвера. Если забыть вернуть настройки, следующий запрос того же долгоживущего обработчика начнёт вести себя иначе. Сохраним исходные значения, сравним изменение через configure и ini_set и восстановим оба представления в finally. В JSON будут четыре снимка: ini_get и sqlsrv_get_config рядом.
- Снять четыре исходные настройки через ini_get и sqlsrv_get_config
- configure: буфер драйвера 64 КБ, ini_get остаётся прежним
- ini_set: обе колонки буфера становятся 96 КБ
- finally возвращает сохранённые INI и значения драйвера
- Снимок после подтверждает восстановление; php.ini не записывался
Сначала короткий вызов
$before = sqlsrv_get_config('WarningsReturnAsErrors');
try {
if (sqlsrv_configure('WarningsReturnAsErrors', 0) === false) {
throw new RuntimeException("Настройка не применена");
}
// Только ограниченная работа, для которой выбрана эта политика.
} finally {
sqlsrv_configure('WarningsReturnAsErrors', $before); // Полный файл проверяет возврат.
}В ini ключ записывается с префиксом sqlsrv., а get_config/configure получают короткое имя. ini_get возвращает строковое представление настройки PHP или false; значения драйвера читаем его API. Для известного булева WarningsReturnAsErrors результат false означает выключенный флаг, поэтому его нельзя автоматически считать ошибкой. У остальных трёх настроек false проверяется до приведения к int. В этом драйвере configure меняет рабочее значение, но не строку ini_get; ini_set меняет оба. Поэтому сначала восстанавливаем INI, затем исходное рабочее значение. Журнал только читается и не переключается.
Результат лабораторного запуска
| Наблюдение | Результат |
|---|---|
| Снимков: до / configure / ini_set / после | 4 |
| WarningsReturnAsErrors после configure | 0 |
| Буфер драйвера после configure, КБ | 64 |
| configure сохранил прежнее значение ini_get | да |
| Буфер драйвера после ini_set, КБ | 96 |
| Буфер ini_get после ini_set, КБ | 96 |
| Исходные INI и значения драйвера восстановлены | да |
| Настройки журнала остались прежними | да |
| Изменялся файл php.ini | нет |
| Открывались соединения с базой | нет |
Полная программа и архив для самостоятельного запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s152Runtime();
try {
if (!extension_loaded('sqlsrv')) {throw new RuntimeException('Для опыта настроек нужен загруженный SQLSRV');}
$names=['WarningsReturnAsErrors','LogSubsystems','LogSeverity','ClientBufferMaxKBSize'];
$snapshot=static function(array $names):array {
$data=[];
foreach ($names as $name) {
$ini=ini_get('sqlsrv.'.$name);
if ($ini===false) {throw new RuntimeException('INI-настройка не найдена: '.$name);}
$value=sqlsrv_get_config($name);
// false is a valid disabled flag only for this known boolean option.
if ($value===false && $name!=='WarningsReturnAsErrors') {s152Failure('Чтение настройки '.$name);}
$data[$name]=['ini'=>$ini,'driver'=>(int)$value];
}
return $data;
};
$before=$snapshot($names);
$wanted=['WarningsReturnAsErrors'=>0,'ClientBufferMaxKBSize'=>64];
try {
foreach ($wanted as $name=>$value) {
if (sqlsrv_configure($name,$value)===false) {s152Failure('Изменение учебной настройки '.$name);}
}
$during=$snapshot($names);
if (ini_set('sqlsrv.ClientBufferMaxKBSize','96')===false) {throw new RuntimeException('Не удалось задать INI-буфер');}
$afterIni=$snapshot($names);
} finally {
// Restore both layers: ini_set synchronizes the driver, configure may change only the driver.
// Logging settings were read but never changed and are not overwritten here.
foreach (array_keys($wanted) as $name) {
if (ini_set('sqlsrv.'.$name,$before[$name]['ini'])===false) {throw new RuntimeException('Восстановление INI-настройки '.$name);}
if (sqlsrv_configure($name,$before[$name]['driver'])===false) {s152Failure('Восстановление настройки драйвера '.$name);}
}
}
$after=$snapshot($names);
$restored=$before===$after;
$loggingUntouched=$before['LogSubsystems']===$after['LogSubsystems'] && $before['LogSeverity']===$after['LogSeverity'];
$rows=[['Снимков: до / configure / ini_set / после',4],['WarningsReturnAsErrors после configure',$during['WarningsReturnAsErrors']['driver']],
['Буфер драйвера после configure, КБ',$during['ClientBufferMaxKBSize']['driver']],
['configure сохранил прежнее значение ini_get',$during['ClientBufferMaxKBSize']['ini']===$before['ClientBufferMaxKBSize']['ini']],
['Буфер драйвера после ini_set, КБ',$afterIni['ClientBufferMaxKBSize']['driver']],
['Буфер ini_get после ini_set, КБ',(int)$afterIni['ClientBufferMaxKBSize']['ini']],
['Исходные INI и значения драйвера восстановлены',$restored],['Настройки журнала остались прежними',$loggingUntouched],
['Изменялся файл php.ini',false],['Открывались соединения с базой',false]];
$checks=['warnings_applied'=>$during['WarningsReturnAsErrors']['driver']===0,'buffer_applied'=>$during['ClientBufferMaxKBSize']['driver']===64,
'configure_keeps_ini'=>$during['ClientBufferMaxKBSize']['ini']===$before['ClientBufferMaxKBSize']['ini'],
'ini_set_updates_both'=>$afterIni['ClientBufferMaxKBSize']['ini']==='96' && $afterIni['ClientBufferMaxKBSize']['driver']===96,
'original_settings_restored'=>$restored,'logging_unchanged'=>$loggingUntouched,'no_database_connection'=>$db===null && $owned===[]];
$observations=['before'=>$before,'after_configure'=>$during,'after_ini_set'=>$afterIni,'after_restore'=>$after,'request_only'=>true,
'configuration_file_written'=>false,'logging_note'=>'Logging settings read, never changed; no server commands or log-event demo.'];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-ini-options','Прочитать настройки, изменить их в запросе и восстановить',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Снимков: до / configure / ini_set / после — 4
WarningsReturnAsErrors после configure — 0
Буфер драйвера после configure, КБ — 64
configure сохранил прежнее значение ini_get — да
Буфер драйвера после ini_set, КБ — 96
Буфер ini_get после ini_set, КБ — 96
Исходные INI и значения драйвера восстановлены — да
Настройки журнала остались прежними — да
Изменялся файл php.ini — нет
Открывались соединения с базой — нет
Запустите отдельный CLI пример по README.txt. К базе программа не подключается; исследуются только текущий PHP и загруженный драйвер. lab.php сохраняет result.html, result.json и metrics.json.
Открыть сохранённый результат · Снимки и проверки JSON.
Границы и ошибки
Эксперимент меняет и проверяет настройки, но не создаёт предупреждение SQL и не доказывает запись строки в системный журнал. Для поведения предупреждений есть отдельная глава диагностики. WarningsReturnAsErrors = 0 не делает настоящие SQL-ошибки успешными; SQLSTATE 01000, 01001, 01003 и 01S02 являются документированными исключениями из превращения предупреждений в false. Не устанавливайте ноль глобально ради скрытия проблемы. sqlsrv_configure не редактирует php.ini. Долгоживущему worker особенно нужен собственный finally; восстановить нужно снятые значения, а не всегда 1/0/10240 из таблицы.
Измените одно требование
До опыта буфер уже был 2048 КБ. Какое значение должно остаться после finally?
Как проверить изменение
2048 КБ, потому что восстанавливается снимок текущей среды. Нельзя безусловно записать стандартные 10240: это затрёт настройку приложения. В отчёте сравниваются именно значения «до» и «после».
Уместить экран в буфер, уменьшив запрос вместо раздувания лимита
Экрану нужны три ID документов после ID 40, но запрос тащит все 100 документов вместе с длинным содержимым. Клиентский буфер в 1 КБ отклоняет большой результат. Оставим тот же бюджет и изменим запрос под экран: TOP (3), один нужный столбец и WHERE. Получим ID 41–43, не загружая payload и не сортируя большой массив в PHP.
- В таблице: 100 документов; payload каждого — 4000 байт
- Buffered SELECT всех полей → целый результат не помещается в 1 КБ
- Сохранить отказ; это не пустая выдача
- Тот же бюджет: SELECT TOP (3) id WHERE id > 40 ORDER BY id
- В PHP приходят только три короткие строки
- Таблица содержит прежние 100 документов; буфер не увеличен
Сначала короткий вызов
$sql = "SELECT TOP (3) id FROM $q WHERE id > ? ORDER BY id";
$stmt = sqlsrv_query($db, $sql, [40], [
'Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED,
'ClientBufferMaxKBSize' => 1,
]);
// Проверить false; затем прочитать три нужные строки.Scrollable выбирает клиентский буфер: весь результат текущего запроса размещается в памяти клиента. ClientBufferMaxKBSize — размер в килобайтах, здесь на уровне одной команды. Это не число строк и не общий memory_limit PHP. TOP (3) ограничивает число возвращаемых строк, WHERE id > ? задаёт следующую страницу, ORDER BY id фиксирует порядок по уникальному ключу. Значение 40 передаётся параметром.
Результат лабораторного запуска
| Наблюдение | Результат |
|---|---|
| Бюджет клиентского буфера, КБ | 1 |
| Большая выдача отклонена | да |
| Диагностика отказа сохранена | да |
| Причина: лимит буфера IMSSP/-59 | да |
| Число строк страницы | 3 |
| ID нужной страницы | 41, 42, 43 |
| Столбцов в строке страницы | 1 |
| Всего строк в таблице после чтения | 100 |
| Лимит пришлось увеличивать | нет |
Полная программа и архив для самостоятельного запуска
<?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);
$items=s152OwnTable($db,$owned,'buffer_budget','(id int NOT NULL PRIMARY KEY, title varchar(24) NOT NULL, payload varchar(4000) NOT NULL)');
$q=s152Qualified($items);
s152Command($db,";WITH n AS (SELECT 1 AS id UNION ALL SELECT id+1 FROM n WHERE id<100)
INSERT INTO $q (id,title,payload) SELECT id,CONCAT('Item-',id),REPLICATE('x',4000) FROM n OPTION (MAXRECURSION 100)");
$options=['Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED,'ClientBufferMaxKBSize'=>1,'QueryTimeout'=>10];
$large=sqlsrv_query($db,"SELECT id,title,payload FROM $q ORDER BY id",[],$options);
$saved=s152Diagnostics(sqlsrv_errors(SQLSRV_ERR_ALL));
$refused=$large===false;
$bufferError=in_array(['SQLSTATE'=>'IMSSP','code'=>-59],$saved,true);
if (is_resource($large)) {sqlsrv_free_stmt($large);}
if (!$refused) {throw new RuntimeException('Ожидался отказ ограниченного клиентского буфера');}
// Same per-statement budget, but only the fields and page that this screen needs.
$small=s152Query($db,"SELECT TOP (3) id FROM $q WHERE id>? ORDER BY id",[40],$options);
try {
$count=sqlsrv_num_rows($small);
if ($count===false) {s152Failure('Количество строк небольшой страницы');}
$page=s152Rows($small);
} finally {sqlsrv_free_stmt($small);}
$ids=implode(', ',array_column($page,'id'));
$unchanged=s152Read($db,"SELECT COUNT(*) AS total FROM $q")[0]['total'];
$rows=[['Бюджет клиентского буфера, КБ',1],['Большая выдача отклонена',$refused],['Диагностика отказа сохранена',count($saved)>0],['Причина: лимит буфера IMSSP/-59',$bufferError],
['Число строк страницы',$count],['ID нужной страницы',$ids],['Столбцов в строке страницы',count($page[0])],
['Всего строк в таблице после чтения',$unchanged],['Лимит пришлось увеличивать',false]];
$checks=['large_result_rejected'=>$refused,'diagnostics_saved'=>count($saved)>0,'buffer_limit_error'=>$bufferError,'three_rows'=>$count===3 && count($page)===3,
'page_ids'=>array_column($page,'id')===[41,42,43],'page_fields'=>array_keys($page[0])===['id'],
'table_unchanged'=>$unchanged===100];
$observations=['buffer_limit_kb'=>1,'attempted_rows'=>100,'payload_bytes_per_large_row'=>4000,
'rejected_query_diagnostic'=>$saved,'requested_page_after_id'=>40,'page'=>$page,'stored_rows_after'=>$unchanged];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-buffer-budget','Уместить экран в буфер, уменьшив запрос вместо раздувания лимита',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Бюджет клиентского буфера, КБ — 1
Большая выдача отклонена — да
Диагностика отказа сохранена — да
Причина: лимит буфера IMSSP/-59 — да
Число строк страницы — 3
ID нужной страницы — 41, 42, 43
Столбцов в строке страницы — 1
Всего строк в таблице после чтения — 100
Лимит пришлось увеличивать — нет
Запустите отдельный CLI пример по README.txt. Нужен отдельный учебный SQL Server; программа создаёт и удаляет одну свою таблицу. lab.php сохраняет result.html, result.json и metrics.json.
Открыть сохранённый результат · Снимки и проверки JSON.
Границы и ошибки
1 КБ — намеренно маленький учебный бюджет, не рекомендация для рабочего сайта. Объём буфера включает служебные расходы, поэтому размер SQL-полей нельзя считать точной оценкой памяти PHP. Увеличение допустимо после расчёта и измерения, но не заменяет ограничение выдачи. Для больших последовательных экспортов рассмотрите forward-курсор и запись по строке; для количества — COUNT на сервере. Не увеличивайте memory_limit вместе с SELECT * автоматически. Эта страница использует ключевую пагинацию по уникальному id; для сортировки по неуникальной дате нужен устойчивый составной ключ и соответствующее условие.
Большая выдача около ста тысяч строк через VPN была медленной; обсуждались буферы, тип курсора и повторные подключения. Исходный вопрос на Stack Overflow. Отделяем ограничение объёма нужного экрана от диагностики сети и соединений. Наш опыт доказывает уменьшение результата и соблюдение бюджета, а не универсальное ускорение в разы. Чужие настройки ClientBufferMaxKBSize = 0 не копируем: допустимый бюджет должен быть положительным.
Измените одно требование
Добавьте payload в маленькую страницу. Можно ли заранее обещать, что три строки поместятся в 1 КБ?
Как проверить изменение
Нет: три поля по 4000 байт уже больше бюджета, ещё до служебных расходов. Если содержимое нужно только при открытии документа, загружайте его отдельным запросом для выбранного ID; если нужно всё сразу, осознанно пересчитайте бюджет и способ чтения.
Закрыть своё соединение, сохранив данные PHP и другое соединение
В приложении несколько операций используют базу, и непонятно, что можно закрыть после чтения. На одной учебной строке покажем три разных объекта: соединение, команду и уже прочитанный массив PHP. Free освобождает команду; её массив остаётся ready, соединение всё ещё работает. Закрытие первого соединения не закрывает второе независимое.
- connect → ресурс соединения A; второй connect → ресурс B
- query(A) → ресурс statement; fetch → обычный массив PHP
- free_stmt → statement больше не используется; массив ready остаётся
- Новая query(A) по-прежнему работает
- close(A) → обращаться к A больше нельзя
- query(B) → ready; собственная учебная таблица убирается через B
Сначала короткий вызов
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Полный файл различает array, null и false.
sqlsrv_free_stmt($stmt);
$stmt = null;
// $row остаётся обычным массивом; соединение $db ещё открыто.Ресурс — дескриптор, через который PHP обращается к состоянию драйвера. Его не нужно преобразовывать в число для SQL или сохранять в JSON как подключение. $row после fetch — отдельный PHP-массив. Присваивание $stmt = null помечает, что прежнюю команду уже нельзя использовать; оно само не заменяет проверенный вызов free_stmt. Два connect в опыте создают два независимых ресурса; ConnectionPooling отключён, чтобы граница была явной.
Результат лабораторного запуска
| Наблюдение | Результат |
|---|---|
| Два соединения имеют разные ресурсы | да |
| Statement освобождён | да |
| Скопированное поле PHP после free_stmt | ready |
| Первое соединение после free_stmt | ready |
| Первое соединение закрыто | да |
| Второе соединение после закрытия первого | ready |
| Освобождённый statement вызывался снова | нет |
Полная программа и архив для самостоятельного запуска
<?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);
$notes=s152OwnTable($db,$owned,'resource_lifetime','(id int NOT NULL PRIMARY KEY, title varchar(24) NOT NULL)');
$q=s152Qualified($notes);
s152Command($db,"INSERT INTO $q (id,title) VALUES (1,'ready')");
$second=s152Connect($config);
$stmt=null;
try {
$firstIsResource=is_resource($db);$secondIsResource=is_resource($second);
$independent=get_resource_id($db)!==get_resource_id($second);
$stmt=s152Query($db,"SELECT id,title FROM $q WHERE id=?",[1]);
$statementType=get_resource_type($stmt);
$copy=sqlsrv_fetch_array($stmt,SQLSRV_FETCH_ASSOC);
if ($copy===false) {s152Failure('Чтение строки перед освобождением');}
if ($copy===null) {throw new RuntimeException('Учебная строка отсутствует');}
$freed=sqlsrv_free_stmt($stmt);
if ($freed===false) {s152Failure('Освобождение statement');}
$stmt=null;
$firstStillWorks=s152Read($db,"SELECT title FROM $q WHERE id=?",[1])[0]['title'];
$closed=sqlsrv_close($db);
if ($closed===false) {s152Failure('Закрытие первого соединения');}
// The cleanup owner now keeps the surviving connection, never the closed handle.
$db=$second;$second=null;
$afterClose=s152Read($db,"SELECT title FROM $q WHERE id=?",[1])[0]['title'];
} finally {
if (is_resource($stmt)) {sqlsrv_free_stmt($stmt);}
if (is_resource($second)) {sqlsrv_close($second);}
}
$rows=[['Два соединения имеют разные ресурсы',$independent],['Statement освобождён',$freed],
['Скопированное поле PHP после free_stmt',$copy['title']],['Первое соединение после free_stmt',$firstStillWorks],
['Первое соединение закрыто',$closed],['Второе соединение после закрытия первого',$afterClose],
['Освобождённый statement вызывался снова',false]];
$checks=['connections_are_resources'=>$firstIsResource && $secondIsResource,'distinct_handles'=>$independent,
'statement_freed'=>$freed===true,'php_copy_survives'=>$copy===['id'=>1,'title'=>'ready'],
'free_not_close'=>$firstStillWorks==='ready','first_connection_closed'=>$closed===true,'other_connection_works'=>$afterClose==='ready'];
$observations=['connection_resources_distinct'=>$independent,'statement_resource_type'=>$statementType,
'php_row_copy'=>$copy,'read_after_free_stmt'=>$firstStillWorks,'read_on_other_connection_after_close'=>$afterClose,
'invalid_handle_called'=>false,'connection_pooling'=>false];
} finally {
if (is_resource($db)) {try {$removed=s152Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s152Report('sqlsrv152-resource-lifetime','Закрыть своё соединение, сохранив данные PHP и другое соединение',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Два соединения имеют разные ресурсы — да
Statement освобождён — да
Скопированное поле PHP после free_stmt — ready
Первое соединение после free_stmt — ready
Первое соединение закрыто — да
Второе соединение после закрытия первого — ready
Освобождённый statement вызывался снова — нет
Запустите отдельный CLI пример по README.txt. Нужен отдельный учебный SQL Server; программа создаёт и удаляет одну свою таблицу. lab.php сохраняет result.html, result.json и metrics.json.
Открыть сохранённый результат · Снимки и проверки JSON.
Границы и ошибки
Не проверяйте «работает ли закрытый дескриптор», повторно вызывая на нём драйвер: неверный тип/ресурс может привести к предупреждению или исключению. Проверяется полезное поведение оставшегося соединения. Копирование $another = $db не создаёт второе соединение — это другая переменная того же ресурса. Здесь второй ресурс получен отдельным connect. В обычном запросе не надо создавать новое соединение на каждую строку: владелец передаёт существующее вниз и закрывает после работы. Незавершённые транзакции завершайте явно; пример не исследует pooling или распределённые транзакции.
Измените одно требование
Если заменить второй connect на $second = $db, останется ли независимое подключение после close первого?
Как проверить изменение
Нет: две переменные будут ссылаться на один ресурс. Это не два connect. Удалите ожидание независимости и определите одного владельца, который закрывает соединение после всех использующих его операций.
Все функции SQLSRV по назначению
Ниже все 26 функций официального раздела SQLSRV, сгруппированные по роли. Каждая ссылка ведёт к уже написанной карточке с параметрами, результатом и примерами; это указатель, а не повтор синтаксиса без объяснений.
Соединение, среда и диагностика
| Функция | Практическое назначение |
|---|---|
| sqlsrv_connect | Открыть соединение |
| sqlsrv_close | Закрыть соединение |
| sqlsrv_client_info | Узнать клиентский драйвер |
| sqlsrv_server_info | Узнать сервер |
| sqlsrv_errors | Сохранить ошибки и предупреждения |
| sqlsrv_configure | Изменить настройки текущей работы |
| sqlsrv_get_config | Прочитать настройки драйвера |
Команда, параметры и данные строки
| Функция | Практическое назначение |
|---|---|
| sqlsrv_query | Разовая параметризованная команда |
| sqlsrv_prepare | Подготовить повторяемую команду |
| sqlsrv_execute | Выполнить подготовленную команду |
| sqlsrv_field_metadata | Получить описание столбцов |
| sqlsrv_fetch_array | Прочитать строку массивом |
| sqlsrv_free_stmt | Освободить команду |
| sqlsrv_rows_affected | Проверить число изменённых строк |
Целостность нескольких изменений
| Функция | Практическое назначение |
|---|---|
| sqlsrv_begin_transaction | Начать явную группу |
| sqlsrv_commit | Подтвердить изменения |
| sqlsrv_rollback | Отменить незавершённую группу |
Чтение и переходы по результатам
| Функция | Практическое назначение |
|---|---|
| sqlsrv_fetch | Выбрать текущую строку |
| sqlsrv_get_field | Прочитать поле текущей строки |
| sqlsrv_fetch_object | Прочитать строку объектом |
| sqlsrv_has_rows | Проверить наличие строк |
| sqlsrv_num_rows | Узнать число строк при подходящем курсоре |
| sqlsrv_num_fields | Узнать число столбцов |
| sqlsrv_next_result | Перейти к следующему результату команды |
Передача потока
| Функция | Практическое назначение |
|---|---|
| sqlsrv_send_stream_data | Передать очередную часть входного потока |
Завершение ненужной выдачи
| Функция | Практическое назначение |
|---|---|
| sqlsrv_cancel | Отбросить остаток результата и сохранить подготовленную команду |
Для выбора констант и направлений параметров далее используйте контракты параметров SQLSRV. Число функций здесь характеризует именно раздел SQLSRV, не полноту всего PHP Manual.
Источники и обсуждения
- PHP Manual — sqlsrv.setup
- PHP Manual — sqlsrv.requirements
- PHP Manual — sqlsrv.installation
- PHP Manual — sqlsrv.configuration
- PHP Manual — sqlsrv.resources
- PHP Manual — ref.sqlsrv
- Microsoft — требования к стеку
- Microsoft — совместимость версий
- Microsoft — загрузка расширений
- Microsoft — Linux и macOS
- Microsoft — настройки SQLSRV
- Microsoft — клиентский буфер
- Microsoft — предупреждения и ошибки
- Microsoft — журналирование
- PHP — php-ini-loaded-file
- PHP — php-ini-scanned-files
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


