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 instalación y limpieza final. 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 válido para ese punto final. 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.

Escribe y lee una línea

Los programas pasan 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 explícitos de transacción El ejemplo trata la operación de escritura como una unidad que debe o bien hacer commit o revertir. 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 solo avance 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 su recuperación de matriz llega al final y false en caso de error de obtenció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 solo a una tabla temporal que la limpieza elimina. 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.

Gestión de errores y limpieza

El catch bloque revierte una transacción activa y vuelve a lanzar el fallo. El finally bloqueador libera la declaración, elimina la tabla temporal y libera la conexión. La limpieza se desarrolla por el camino del éxito y el camino del fracaso. 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, capta la información en el código que gestiona la solicitud. Elimina valores sensibles antes de registrar diagnósticos en tu registro de aplicaciones y devuelva 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 función de extensión o controlador Confirma que el proceso PHP carga la extensión prevista con el tiempo de ejecución y la configuración correspondientes. 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 de producción.
No se ha encontrado la tabla temporal Mantén la configuración inicial, las escrituras y las lecturas en una sola conexión. Para la configuración de PDO, mantener el enfoque mostrado exec en lugar de sustituirlo por una llamada preparada query por defecto.
Valor inesperado o tipo PHP Comprueba los atributos de fetch y los descriptores de parámetros. No sustituyas las afirmaciones estrictas por comparaciones vagas para ocultar la diferencia.
Fallo de escritura o de reversión Captura los errores de SQLSRV inmediatamente, o la excepción PDO. También informa de fallos de rollback; no digas que la escritura se deshizo si falló.
Fallo de limpieza tras pérdida de conexión El programa no puede confirmar la caída 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 alcance temporal de la mesa, las transacciones, las restricciones y los datos comprometidos. 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, base de datos SQL en Fabric y Warehouse en Fabric, primero comprueba la autenticación y la compatibilidad con T-SQL del servicio de destino. 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 soporte específico del servicio para transacciones o tablas temporales.

Elige tu próxima tarea.

Extiende el flujo de trabajo con el artículo sobre el comportamiento que necesitas.

Tarea Guidance
Elige la autenticación o gestiona 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 Gestión de errores y advertencias y actividad de registro.
Proteger las columnas sensibles Usando Always Encrypted con los controladores de Microsoft para PHP para SQL Server.
Proteger una aplicación de producción Consideraciones de seguridad para los controladores de Microsoft para PHP para SQL Server.