Guía de programación para los controladores de Microsoft para PHP para SQL Server

Descargar controlador PHP

Después de tener una conexión funcional desde Getting Started, usa esta guía para escribir, hacer commit y leer una fila con cualquiera de las APIs de PHP. Cada programa incluye su propia configuración y limpieza. Utiliza los enlaces de tareas para ampliar el flujo de trabajo en lugar de copiar opciones de una API a otra.

Prepara una conexión de prueba

Utiliza una base de datos de prueba de SQL Server existente y una cuenta de inicio de sesión que pueda conectarse y crear tablas temporales locales. Los ejemplos utilizan autenticación SQL. Para opciones de autenticación compatibles y la configuración inicial de Azure sin contraseña, véase Conexión al servidor.

Carga la extensión que elijas y establece SQL_SERVER, SQL_DATABASE, SQL_USER, y SQL_PASSWORD en el entorno del proceso PHP. SQL_SERVER puede incluir un puerto explícito, como <server>,1433. El servidor debe presentar un certificado en el que confíe el cliente y que sea válido para ese punto de conexión. Mantén las contraseñas fuera del programa y del control de versiones.

Almacena el nombre real de la base de datos, nombre de usuario y contraseña en las variables de entorno. Cada programa duplica las llaves de cierre y encierra estos valores entre llaves una vez para el manejo de conexiones estilo ODBC del controlador PHP. Escaping preserva caracteres especiales en los valores de conexión; no es parametrización de consulta. Para más información, consulta Conexión al servidor.

Las sentencias Transact-SQL (T-SQL) crean #PhpGuide, una tabla temporal visible solo para la conexión que la crea. Los programas no crean ni cambian una tabla de aplicaciones permanente. Mantén la misma conexión durante todo el flujo de trabajo.

Escribir y leer una fila

Los programas insertan un entero, texto UTF-8 y SQL NULL a través de parámetros. Requieren exactamente una fila afectada, hacer commit de la transacción y verificar los valores devueltos y los tipos de PHP. La consulta final también comprueba que no quede ninguna transacción abierta.

SQLSRV

Guarda este programa completo como workflow-sqlsrv.php, y ejecuta 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

Guarda este programa separado como workflow-pdo.php, y ejecuta 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;

Salida esperada para cualquiera de los programas:

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

La salida aparece solo después de que las comprobaciones y la limpieza tengan éxito. Un fallo de conexión, escritura, obtención, rollback o limpieza de tablas genera una excepción en su lugar.

Entiende las opciones

Usa ? marcadores de posición para los valores en lugar de concatenar los datos de entrada en T-SQL. Los ejemplos mantienen fijos los nombres de tabla y columna: un parámetro de valor no puede reemplazar un identificador. Las sentencias preparadas también te permiten ejecutar la misma sentencia de nuevo con valores diferentes. Para más información, véase Comparar funciones de ejecución de consultas.

Elección Reason
Métodos de transacción explícitos El ejemplo trata la escritura como una unidad que debe confirmarse o revertirse. SQLSRV utiliza sqlsrv_begin_transaction, sqlsrv_commit, y sqlsrv_rollback; PDO utiliza sus propios métodos de transacción.
Descriptores de parámetros SQLSRV Cada descriptor especifica dirección, tipo PHP y tipo SQL. Las referencias conservan las variables de entrada, incluida la variable que contiene null.
PDO bindValue La sentencia recibe cada valor actual con un tipo explícito de parámetro PDO. bindParam es apropiado cuando las ejecuciones posteriores deben leer una variable por referencia.
UTF-8 más nvarchar PHP suministra bytes UTF-8; la extensión y el controlador ODBC los convierten para la columna SQL Unicode. El texto recuperado debe coincidir exactamente, byte por byte, con la cadena PHP original.
Obtención numérica explícita para PDO La comparación de resultados espera enteros PHP. Sin el atributo PDO_SQLSRV, estos resultados enteros SQL utilizan la representación en cadena predeterminada del controlador.
Claves de recuperación con nombre SQLSRV_FETCH_ASSOC y PDO::FETCH_ASSOC devuelven los arrays indexados por nombre de columna. Las constantes de la API no son intercambiables.
PDO::exec para la configuración de la tabla Ejecuta directamente la sentencia de creación de tabla. Este enfoque evita crear una tabla temporal dentro del alcance de una instrucción preparada por el servidor.
Resultados de avance único Los programas leen cada fila una vez. No solicitan un resultado con búfer ni usan un recuento de filas SELECT como prueba de que la obtención ha tenido éxito.

SQLSRV devuelve null cuando la recuperación de la matriz llega al final y false en caso de error de recuperación. El fetch de PDO devuelve false al final; con el modo de excepción configurado, se lanza un error de obtención en la base de datos. En ambas APIs, el OptionalCount campo contiene PHP null. Ese valor de campo no es un marcador de final de resultados.

La operación de escritura se confirma antes de la consulta de verificación. Si una lectura posterior falla, la escritura confirmada anterior no puede revertirse. En este ejemplo, la fila pertenece únicamente a una tabla temporal que se elimina durante la limpieza. En una aplicación con tablas permanentes, informa del fallo con precisión y no repitas a ciegas una inserción tras un resultado incierto del commit.

Administración de errores y limpieza

El bloque catch revierte una transacción activa y vuelve a lanzar el error. El bloque finally libera la instrucción, elimina la tabla temporal y libera la conexión. La limpieza se ejecuta tanto en la ruta de éxito como en la de fallo. Si el rollback o la limpieza de tablas también fallan, el programa conserva la excepción original como una excepción anterior.

Para un ejercicio de línea de comandos, una excepción no capturada detiene PHP con un código de salida distinto de cero y un mensaje de error. En una aplicación web, captúralo en el código que administra la solicitud. Elimina los valores confidenciales antes de registrar los datos de diagnóstico en el registro de tu aplicación y devuelve una respuesta de error adecuada. No expongas detalles de conexión, credenciales ni errores en bruto de la base de datos al navegador.

Failure Elementos que se deben comprobar
Falta una función de extensión o un controlador Comprueba que el proceso de PHP carga la extensión prevista con el tiempo de ejecución y la configuración adecuados. Una configuración de PHP de línea de comandos funcional no demuestra que tu servidor web lo utilice.
Fallo de conexión o de certificado Comprueba el endpoint, la autenticación, los permisos y la confianza del certificado del cliente antes de cambiar el código de consulta. No desactives la validación de certificados como solución en producción.
No se ha encontrado la tabla temporal Mantén la configuración, las escrituras y las lecturas en una sola conexión. Para la configuración de PDO, mantén la ruta exec mostrada en lugar de sustituirla por una llamada preparada query predeterminada.
Valor inesperado o tipo PHP Comprueba los atributos de fetch y los descriptores de parámetros. No sustituyas las afirmaciones estrictas por comparaciones imprecisas para ocultar la diferencia.
Error de escritura o de reversión Captura inmediatamente los errores de SQLSRV o la excepción de PDO. Informa también de los errores de reversión; no afirmes que la escritura se ha deshecho si la reversión ha fallado.
Fallo de limpieza tras pérdida de conexión El programa no puede confirmar la eliminación en el lado del servidor. Liberar los handles PHP no demuestra que la conexión física subyacente agrupada se cerrara de inmediato.

Las extensiones PHP proporcionan las APIs de binding y fetch. ODBC se comunica con el servidor y gestiona la agrupación de conexiones. SQL Server controla el ámbito de las tablas temporales, las transacciones, las restricciones y los datos confirmados. Elimina la tabla explícitamente en lugar de depender de que la conexión física se cierre para la limpieza.

Después del ejercicio, elimina workflow-sqlsrv.php y workflow-pdo.php si ya no los necesitas. Borra las variables del entorno de credenciales del proceso de prueba cuando termines. Ninguno de los dos programas deja una tabla permanente que haya que eliminar.

Estas guías están dirigidas a SQL Server. Para Azure SQL Database, Azure SQL Managed Instance, Azure Synapse Analytics, la base de datos SQL en Fabric y Warehouse en Fabric, comprueba primero la autenticación del destino y la compatibilidad con T-SQL. Ambos productos de Fabric requieren autenticación de Microsoft Entra ID, y Warehouse requiere que los conjuntos de resultados activos múltiples (MARS) estén deshabilitados. Estos programas de SQL Server no establecen compatibilidad con transacciones específicas del servicio ni con tablas temporales.

Elige tu próxima tarea.

Amplía el flujo de trabajo con el artículo correspondiente al comportamiento que necesitas.

Tarea Guidance
Elegir la autenticación o administrar las conexiones Conectando al servidor.
Reutilizar una sentencia preparada con distintos valores Compara las funciones de ejecución de consultas.
Elige la ejecución directa o preparada de PDO Ejecución directa de la declaración y ejecución de la declaración preparada en el controlador PDO_SQLSRV.
Obtén valores y elige las formas de resultado Recuperando datos.
Desplazar o almacenar un resultado en búfer Especificar un tipo de cursor y seleccionar filas.
Cambiar datos de la aplicación Actualizando datos.
Controlar las conversiones de tipos de PHP y SQL Convertir tipos de datos.
Diagnosticar fallos y habilitar el registro del controlador Administración de errores y advertencias y Registro de actividad.
Proteger las columnas sensibles Usando Always Encrypted con los controladores de Microsoft para PHP para SQL Server.
Fortalecer una aplicación de producción Consideraciones de seguridad para los controladores de Microsoft para PHP para SQL Server.