Руководство по программированию драйверов Microsoft для PHP для SQL Server

Скачать драйвер PHP

После того как у вас будет рабочее соединение из Getting started, используйте это руководство для записи, коммита и чтения строки с помощью любого из PHP API. Каждая программа включает собственную настройку и очистку. Используйте ссылки на задачи для расширения рабочего процесса, а не копируйте опции из одного API в другой.

Настройте тестовое подключение

Используйте существующую тестовую базу данных SQL Server и логин с правом подключения и создания локальных временных таблиц. Примеры используют SQL-аутентификацию. Для поддерживаемых вариантов аутентификации и беспарольного подключения к Azure см. раздел «Подключение к серверу».

Загрузите выбранное расширение и установите SQL_SERVER, SQL_DATABASE, SQL_USER, и SQL_PASSWORD в среде процесса PHP. SQL_SERVER может включать явный порт, например <server>,1433. Сервер должен предоставить сертификат, которому клиент доверяет и который действителен для этой конечной точки. Держите пароли вне программы и системы контроля версий.

Храните фактическое имя базы данных, имя пользователя и пароль в переменных среды. Каждая программа удваивает закрывающие фигурные скобки и заключает эти значения в фигурные скобки один раз для обработки соединения драйвера PHP в стиле ODBC. Экранирование сохраняет специальные символы в значениях соединения; это не параметризация запросов. Для получения дополнительной информации см. раздел «Подключение к серверу».

Операторы Transact-SQL (T-SQL) создают #PhpGuideвременную таблицу, видимую только соединению, которое её создаёт. Программы не создают и не меняют постоянную таблицу приложения. Сохраняйте то же подключение на протяжении всего рабочего процесса.

Пиши и читай ряд

Программы вставляют целое число, текст UTF-8 и SQL NULL через параметры. Они требуют ровно одну затронутую строку, фиксируют транзакцию и проверяют возвращённые значения и типы PHP. Последний запрос также проверяет, что ни одна транзакция не остаётся открытой.

SQLSRV

Сохраните эту полную программу как workflow-sqlsrv.php, и запустите php workflow-sqlsrv.php.

<?php
$config = [];
foreach (['SQL_SERVER', 'SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $value = getenv($name);
    if ($value === false || $value === '') {
        throw new RuntimeException("Set $name before running this example.");
    }
    $config[$name] = $value;
}
foreach (['SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $config[$name] = '{' . str_replace('}', '}}', $config[$name]) . '}';
}
$conn = sqlsrv_connect($config['SQL_SERVER'], [
    'Database' => $config['SQL_DATABASE'],
    'UID' => $config['SQL_USER'],
    'PWD' => $config['SQL_PASSWORD'],
    'Encrypt' => true,
    'TrustServerCertificate' => false,
    'LoginTimeout' => 30,
    'CharacterSet' => 'UTF-8',
]);
if ($conn === false) {
    throw new RuntimeException(print_r(sqlsrv_errors(), true));
}
$stmt = null;
$inTransaction = false;
$failure = null;
try {
    $stmt = sqlsrv_query($conn, 'CREATE TABLE #PhpGuide '
        . '(Id int PRIMARY KEY, Title nvarchar(80), OptionalCount int NULL)');
    if ($stmt === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    sqlsrv_free_stmt($stmt);
    $stmt = null;
    if (!sqlsrv_begin_transaction($conn)) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    $inTransaction = true;
    $id = 1;
    $title = "Caf\u{00E9}";
    $optionalCount = null;
    $params = [
        [&$id, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_INT, SQLSRV_SQLTYPE_INT],
        [&$title, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('UTF-8'), SQLSRV_SQLTYPE_NVARCHAR(80)],
        [&$optionalCount, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_INT, SQLSRV_SQLTYPE_INT],
    ];
    $stmt = sqlsrv_prepare($conn,
        'INSERT INTO #PhpGuide (Id, Title, OptionalCount) VALUES (?, ?, ?)', $params);
    if ($stmt === false || !sqlsrv_execute($stmt)) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    $affected = sqlsrv_rows_affected($stmt);
    if ($affected === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    if ($affected !== 1) {
        throw new RuntimeException('Expected exactly one inserted row.');
    }
    sqlsrv_free_stmt($stmt);
    $stmt = null;
    if (!sqlsrv_commit($conn)) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    $inTransaction = false;
    $stmt = sqlsrv_query($conn,
        'SELECT Id, Title, OptionalCount, @@TRANCOUNT AS TransactionCount FROM #PhpGuide');
    if ($stmt === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
    if ($row === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    if ($row !== ['Id' => 1, 'Title' => $title, 'OptionalCount' => null, 'TransactionCount' => 0]) {
        throw new RuntimeException('Unexpected committed values, types, or transaction state.');
    }
    $end = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
    if ($end === false) {
        throw new RuntimeException(print_r(sqlsrv_errors(), true));
    }
    if ($end !== null) {
        throw new RuntimeException('Expected exactly one result row.');
    }
} catch (Throwable $error) {
    $failure = $error;
    if ($inTransaction && !sqlsrv_rollback($conn)) {
        $failure = new RuntimeException(
            'Rollback failed: ' . print_r(sqlsrv_errors(), true), 0, $error);
    }
    throw $failure;
} finally {
    if (is_resource($stmt)) {
        sqlsrv_free_stmt($stmt);
    }
    try {
        $cleanup = sqlsrv_query($conn, 'DROP TABLE IF EXISTS #PhpGuide');
        if ($cleanup === false) {
            throw new RuntimeException(
                'Cleanup failed: ' . print_r(sqlsrv_errors(), true), 0, $failure);
        }
        sqlsrv_free_stmt($cleanup);
    } finally {
        sqlsrv_close($conn);
    }
}
echo "Inserted 1 row; read committed UTF-8 text and null; no open transaction.", PHP_EOL;

PDO_SQLSRV

Сохраните эту отдельную программу как workflow-pdo.php, и запустите php workflow-pdo.php.

<?php
$config = [];
foreach (['SQL_SERVER', 'SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $value = getenv($name);
    if ($value === false || $value === '') {
        throw new RuntimeException("Set $name before running this example.");
    }
    $config[$name] = $value;
}
foreach (['SQL_DATABASE', 'SQL_USER', 'SQL_PASSWORD'] as $name) {
    $config[$name] = '{' . str_replace('}', '}}', $config[$name]) . '}';
}
$server = $config['SQL_SERVER'];
$database = $config['SQL_DATABASE'];
$pdo = new PDO(
    "sqlsrv:Server=$server;Database=$database;Encrypt=true;"
    . "TrustServerCertificate=false;LoginTimeout=30",
    $config['SQL_USER'],
    $config['SQL_PASSWORD'],
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8,
        PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
    ]
);
$stmt = null;
$failure = null;
try {
    $pdo->exec('CREATE TABLE #PhpGuide '
        . '(Id int PRIMARY KEY, Title nvarchar(80), OptionalCount int NULL)');
    $pdo->beginTransaction();
    $title = "Caf\u{00E9}";
    $stmt = $pdo->prepare(
        'INSERT INTO #PhpGuide (Id, Title, OptionalCount) VALUES (?, ?, ?)');
    $stmt->bindValue(1, 1, PDO::PARAM_INT);
    $stmt->bindValue(2, $title, PDO::PARAM_STR);
    $stmt->bindValue(3, null, PDO::PARAM_NULL);
    $stmt->execute();
    if ($stmt->rowCount() !== 1) {
        throw new RuntimeException('Expected exactly one inserted row.');
    }
    $stmt = null;
    $pdo->commit();
    $stmt = $pdo->query(
        'SELECT Id, Title, OptionalCount, @@TRANCOUNT AS TransactionCount FROM #PhpGuide');
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if ($row !== ['Id' => 1, 'Title' => $title, 'OptionalCount' => null, 'TransactionCount' => 0]) {
        throw new RuntimeException('Unexpected committed values, types, or transaction state.');
    }
    if ($stmt->fetch(PDO::FETCH_ASSOC) !== false || $pdo->inTransaction()) {
        throw new RuntimeException('Expected one result row and no open transaction.');
    }
} catch (Throwable $error) {
    $failure = $error;
    if ($pdo->inTransaction()) {
        try {
            $pdo->rollBack();
        } catch (PDOException $rollbackError) {
            $failure = new RuntimeException(
                'Rollback failed: ' . $rollbackError->getMessage(), 0, $error);
        }
    }
    throw $failure;
} finally {
    $stmt = null;
    try {
        $pdo->exec('DROP TABLE IF EXISTS #PhpGuide');
    } catch (PDOException $cleanupError) {
        throw new RuntimeException(
            'Cleanup failed: ' . $cleanupError->getMessage(), 0, $failure ?? $cleanupError);
    } finally {
        $pdo = null;
    }
}
echo "Inserted 1 row; read committed UTF-8 text and null; no open transaction.", PHP_EOL;

Ожидаемый результат для любой из программ:

Inserted 1 row; read committed UTF-8 text and null; no open transaction.

Вывод появляется только после успешного завершения проверок и очистки. Сбой соединения, записи, извлечения, отката или очистки таблицы вместо этого вызывает исключение.

Разберитесь в вариантах

Используйте ? заполнители для значений вместо конкатенации входных данных в T-SQL. В примерах таблица и имена столбцов остаются фиксированными: параметр значения не может заменить идентификатор. Подготовленные выражения также позволяют выполнить одно и то же выражение с разными значениями. Для получения дополнительной информации см. Сравните функции выполнения запросов.

Выбор Reason
Явные методы транзакций В примере запись рассматривается как единое целое, которое должно быть либо зафиксировано, либо отменено. SQLSRV использует sqlsrv_begin_transaction, sqlsrv_commit, и sqlsrv_rollback; PDO использует собственные методы транзакций.
Дескрипторы параметров SQLSRV Каждый дескриптор указывает направление, тип PHP и тип SQL. Ссылки сохраняют входные переменные, включая переменную, содержащую null.
PDO bindValue Оператор получает каждое текущее значение с явным типом параметра PDO. bindParam уместна, когда последующие выполнения должны читать переменную по ссылке.
UTF-8 плюс nvarchar PHP предоставляет байты UTF-8; расширение и драйвер ODBC преобразуют их для столбца SQL Unicode. Полученный текст должен совпадать с исходной строкой PHP байт в байт.
Явная числовая выборка для PDO Результат сравнения предполагает PHP-целые числа. Без атрибута PDO_SQLSRV эти целочисленные результаты SQL используют стандартное строковое представление драйвера.
Названные ключи для получения SQLSRV_FETCH_ASSOC и PDO::FETCH_ASSOC возвращают массивы, индексируемые по названию столбцов. Константы API не взаимозаменяемы.
PDO::exec для настройки таблицы Он выполняет оператор создания таблицы напрямую. Этот подход позволяет избежать создания временной таблицы в области действия подготовленного сервером оператора.
Результаты только для нападающих Программы читают каждую строку по одному разу. Они не запрашивают буферизованный результат и не используют количество строк SELECT как доказательство успешного получения данных.

SQLSRV возвращается null , когда его массив достигает конца и false при ошибке извлечения. Метод fetch PDO возвращает false в конце; при настройке режима исключений ошибка выборки из базы данных приводит к выбросу исключения. В обоих OptionalCount API само поле содержит PHP null. Это значение поля не является маркером окончания результатов.

Запись фиксирует перед проверяющим запросом. Если позднее чтение не удалось, ранее зафиксированную запись нельзя откатить назад. В этом примере строка принадлежит только временной таблице, которая выпадает из очистки. В приложении с постоянными таблицами точно сообщайте о сбое и не повторяйте вставку слепо после неопределённого результата фиксации.

Обработка отказа и очистка

Блок catch откатывает активную транзакцию и повторно вызывает исключение. Блокировка finally освобождает оператор, отбрасывает временную таблицу и отключает соединение. Очистка выполняется в случае успеха и в случае сбоя. Если откат или очистка таблицы тоже не удаются, программа сохраняет исходное исключение как предыдущее.

Для командной строки неперехваченное исключение останавливает PHP с ненулевым кодом выхода и сообщением об ошибке. В веб-приложении поймать это в коде, который обрабатывает запрос. Удалите чувствительные значения перед записью диагностики в журнале приложения и верните соответствующий ответ об ошибке. Не раскрывайте данные соединения, учетные данные или ошибки базы данных браузеру.

Failure Что требуется проверить
Функция расширения или драйвер отсутствует Убедитесь, что процесс PHP загружает заданное расширение с соответствующей средой выполнения и конфигурацией. Рабочая конфигурация PHP в командной строке не доказывает, что ваш веб-сервер его использует.
Сбой подключения или ошибка сертификата Проверьте конечную точку, аутентификацию, разрешения и доверие к сертификатам клиента перед изменением кода запроса. Не отключайте валидацию сертификата как решение для продакшена.
Временная таблица не найдена Выполняйте настройку, запись и чтение через одно соединение. Для настройки PDO сохраняйте продемонстрированный exec способ вместо подстановки стандартного подготовленного вызова query.
Неожиданное значение или тип PHP Проверьте атрибуты выбора и дескрипторы параметров. Не заменяйте строгие проверки на нестрогие сравнения, чтобы скрыть разницу.
Неудача при записи или откате Немедленно перехватывайте либо ошибки SQLSRV, либо исключение PDO. Также сообщайте о неудачах отката; не утверждайте, что запись была отменена, если откат не прошёл.
Сбой очистки после отключения соединения Программа не может подтвердить разрыв соединения на стороне сервера. Выпуск PHP-деки не доказывает, что базовое физическое соединение в пуле сразу закрылось.

Расширения PHP предоставляют API связывания и выборки данных. ODBC взаимодействует с сервером и управляет пулом соединений. SQL Server управляет временным объёмом таблиц, транзакциями, ограничениями и фиксированными данными. Убрать стол явно, а не полагаться на закрытие физического соединения для очистки.

После упражнения удалите workflow-sqlsrv.php и workflow-pdo.php, если они вам больше не нужны. Очистите переменные среды квалификации из процесса тестирования, когда закончите. Ни одна из программ не оставляет постоянной таблицы, которую нужно удалять.

Эти пошаговые руководства ориентированы на SQL Server. Для База данных SQL Azure, Управляемый экземпляр SQL Azure, Azure Synapse Analytics, SQL database in Fabric и Warehouse in Fabric сначала проверьте параметры аутентификации целевого объекта и поддержку T-SQL. Оба продукта Fabric требуют аутентификации Microsoft Entra ID, а Warehouse требует отключения нескольких активных наборов результатов (MARS). Эти программы SQL Server не устанавливают специфичные для сервиса транзакции или поддержку временных таблиц.

Выберите следующую задачу

Расширьте рабочий процесс с помощью статьи для нужного сценария.

Задача Guidance
Выберите способ аутентификации или управление подключениями Подключение к серверу.
Используйте подготовленное выражение с разными значениями Сравните функции выполнения запросов.
Выберите PDO direct или подготовленное выполнение Прямое выполнение инструкций и выполнение подготовленных инструкций в драйвере PDO_SQLSRV.
Получайте значения и выбирайте формы результатов Загрузка данных.
Прокрутите или сохраните результат в буфер Указание типа курсора и выбор строк.
Изменить данные приложения Обновление данных.
Контроль приведения типов PHP и SQL Преобразование типов данных.
Диагностируйте сбои и включите журналирование драйвера Обработка ошибок и предупреждений и ведение логов.
Защитить чувствительные столбцы Использование Always Encrypted с драйверами Microsoft для PHP для SQL Server.
Повышение защищённости рабочего приложения Вопросы безопасности Microsoft Drivers for PHP for SQL Server.