Guia de programação para os drivers Microsoft para PHP para SQL Server

Descarregar o driver PHP

Depois de teres uma ligação a funcionar desde o Getting Started, usa este guia para escrever, fazer commit e ler uma linha com uma das duas APIs PHP. Cada programa inclui a sua própria configuração e limpeza. Utilize os links de tarefas para estender o fluxo de trabalho em vez de copiar opções de uma API para outra.

Prepare uma ligação de teste

Use uma base de dados de teste do SQL Server existente e um início de sessão que possa ligar e criar tabelas temporárias locais. Os exemplos utilizam autenticação SQL. Para escolhas de autenticação suportadas e integração do Azure sem palavra-passe, veja Ligação ao servidor.

Carrega a extensão que escolheres, define SQL_SERVER, SQL_DATABASE, SQL_USER, e SQL_PASSWORD no ambiente do processo PHP. SQL_SERVER pode incluir uma porta explícita, como <server>,1433. O servidor deve apresentar um certificado em que o cliente confie e válido para esse endpoint. Mantém as palavras-passe fora do programa e do controlo de versão.

Armazene o nome real da base de dados, nome de utilizador e palavra-passe nas variáveis de ambiente. Cada programa duplica as chaves de fecho e envolve estes valores em chaves uma vez para a gestão da ligação ao estilo ODBC do controlador PHP. Escapar preserva caracteres especiais nos valores da ligação; não é parametrização de consulta. Para mais informações, consulte Ligação ao servidor.

As instruções Transact-SQL (T-SQL) criam #PhpGuide, uma tabela temporária visível apenas para a conexão que a cria. Os programas não criam nem alteram uma tabela da aplicação permanente. Mantém a mesma conexão durante todo o fluxo de trabalho.

Escreve e lê um registo

Os programas inserem um inteiro, texto UTF-8 e SQL NULL através de parâmetros. Exigem exatamente uma linha afetada, fazem commit da transação e verificam os valores devolvidos e os tipos PHP. A consulta final também verifica que nenhuma transação permanece aberta.

SQLSRV

Guarde este programa completo como workflow-sqlsrv.php, e execute 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

Guarde este programa separado como workflow-pdo.php, e execute 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;

Saída esperada para qualquer um dos programas:

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

A saída só aparece depois de as verificações e a limpeza terem sucesso. Uma falha de ligação, escrita, obtição, rollback ou limpeza de tabela levanta uma exceção em vez disso.

Compreenda as escolhas

Utilize ? marcadores de posição para valores em vez de concatenar a entrada em T-SQL. Os exemplos mantêm fixos os nomes da tabela e das colunas: um parâmetro de valor não pode substituir um identificador. As instruções preparadas também permitem executar a mesma instrução novamente com valores diferentes. Para mais informações, consulte Comparar funções de execução de consultas.

Opção Justificação
Métodos explícitos de transação O exemplo trata a escrita como uma unidade que tem de fazer commit ou fazer rollback. O SQLSRV utiliza sqlsrv_begin_transaction, sqlsrv_commit, e sqlsrv_rollback; O PDO utiliza os seus próprios métodos de transação.
Descritores de parâmetros SQLSRV Cada descritor especifica direção, tipo PHP e tipo SQL. As referências mantêm as variáveis de entrada, incluindo a variável que contém null.
PDO bindValue A instrução recebe cada valor atual com um tipo explícito de parâmetro PDO. bindParam é apropriado quando execuções posteriores têm de ler uma variável por referência.
UTF-8 plus nvarchar O PHP fornece bytes UTF-8; a extensão e o driver ODBC convertem-nos para a coluna Unicode SQL. O texto obtido deve corresponder, byte por byte, à string PHP original.
Obtenção numérica explícita para PDO A comparação de resultados espera inteiros PHP. Sem o atributo PDO_SQLSRV, estes resultados inteiros SQL usam a representação predefinida em string do controlador.
Chaves fetch com nome SQLSRV_FETCH_ASSOC e PDO::FETCH_ASSOC devolvem os arrays indexados pelo nome da coluna. As constantes da API não são intercambiáveis.
PDO::exec para configuração da tabela Executa diretamente a instrução de criação de tabela. Esta abordagem evita criar uma tabela temporária dentro do âmbito de uma instrução preparada pelo servidor.
Resultados apenas de avanço Os programas leem cada linha uma vez. Eles não pedem um resultado em memória nem usam uma contagem de linhas de SELECT como prova de que a obtenção teve sucesso.

O SQLSRV retorna null quando o seu array fetch chega ao fim e false após um erro de fetch. O fetch do PDO retorna false no final; com o modo exceção configurado, é lançada uma exceção de erro de busca da base de dados. Em ambas as APIs, o OptionalCount campo em si contém PHP null. Esse valor de campo não é um marcador de fim de resultados.

A escrita é confirmada antes da consulta de verificação. Se uma leitura posterior falhar, a gravação confirmada anterior não pode ser revertida. Neste exemplo, a linha pertence apenas a uma tabela temporária que a limpeza elimina. Numa aplicação com tabelas permanentes, reporte a falha com precisão e não repita cegamente um insert após um resultado de commit incerto.

Lidar com falhas e limpeza de recursos

O bloco catch anula uma transação ativa e lança novamente a exceção. O bloco finally liberta a declaração, remove a tabela temporária e liberta a ligação. A limpeza é executada no caminho de sucesso e no caminho de falha. Se o rollback ou a limpeza de tabelas também falharem, o programa preserva a exceção original como exceção anterior.

Para um exercício de linha de comandos, uma exceção não apanhada interrompe o PHP com um código de saída não nulo e uma mensagem de erro. Numa aplicação web, capture-o no código que gere o pedido. Remova valores sensíveis antes de registar os diagnósticos no registo da sua aplicação e devolva uma resposta de erro apropriada. Não exponha detalhes de ligação, credenciais ou erros brutos da base de dados ao navegador.

Failure O que deve verificar
A função de extensão ou o controlador está em falta Confirme que o processo PHP carrega a extensão pretendida com o ambiente de execução e configuração correspondentes. Uma configuração PHP funcional na linha de comandos não prova que o seu servidor web o utiliza.
Falha de ligação ou de certificado Verifique o endpoint, autenticação, permissões e confiança do certificado do cliente antes de alterar o código de consulta. Não desative a validação de certificados como solução de produção.
A tabela temporária não foi encontrada Mantém a configuração, as escritas e as leituras numa só conexão. Para a configuração do PDO, mantenha o caminho demonstrado exec em vez de substituir por uma chamada preparada query por defeito.
Valor inesperado ou tipo PHP Verifique atributos de fetch e descritores de parâmetros. Não substituas asserções estritas por comparações flexíveis para esconder a diferença.
Falha de escrita ou de reversão Capture imediatamente os erros do SQLSRV ou capture a exceção PDO. Reporte também falhas de rollback; não diga que a escrita foi desfeita se o rollback falhou.
Falha na limpeza após perda de conexão O programa não consegue confirmar a queda do lado do servidor. A libertação dos handles PHP não prova que a ligação física subjacente em pool fechou imediatamente.

As extensões PHP fornecem as APIs de binding e fetch. O ODBC comunica com o servidor e gere o pooling de ligações. O SQL Server controla o âmbito da tabela temporária, transações, restrições e dados comprometidos. Elimine a tabela explicitamente em vez de depender do fecho da ligação física para a limpeza.

Depois do exercício, apaga workflow-sqlsrv.php e workflow-pdo.php se já não precisares deles. Elimina as variáveis do ambiente das credenciais do processo de teste quando terminares. Nenhum dos programas deixa qualquer tabela permanente.

Estes guias práticos destinam-se ao SQL Server. Para Base de Dados SQL do Azure, Azure SQL Managed Instance, Azure Synapse Analytics, base de dados SQL no Fabric e Warehouse no Fabric, verifique primeiro a autenticação do destino e o suporte a T-SQL. Ambos os produtos Fabric requerem autenticação Microsoft Entra ID, e o Warehouse requer que múltiplos conjuntos de resultados ativos (MARS) sejam desativados. Estes programas do SQL Server não estabelecem suporte a transações específicas de serviço ou a tabelas temporárias.

Escolhe a tua próxima tarefa

Estende o fluxo de trabalho com o artigo sobre o comportamento de que precisas.

Task Documentação de orientação
Escolher autenticação ou gerir ligações A ligar ao servidor.
Reutilizar uma prepared statement com valores diferentes Compare as funções de execução da consulta.
Escolha a execução direta ou preparada do PDO Execução direta da declaração e executação da instrução preparada no driver PDO_SQLSRV.
Buscar valores e escolher os formatos de resultado A recuperar dados.
Deslocar ou armazenar em buffer um resultado Especificar um tipo de cursor e selecionar linhas.
Alterar dados da aplicação A atualizar dados.
Controlo das conversões de tipos PHP e SQL Conversão de tipos de dados.
Diagnosticar falhas e ativar o registo de drivers Tratar erros e avisos e registo de atividade.
Proteger colunas sensíveis Usar Always Encrypted com os drivers Microsoft para PHP para SQL Server.
Reforce a segurança de uma aplicação em produção Considerações de segurança para os drivers Microsoft para PHP para SQL Server.