Getting started의 단계로 연결이 설정되면, 이 가이드를 사용해 PHP API 중 하나로 행을 작성, 커밋, 읽으세요. 각 프로그램마다 별도의 설치 및 제거가 포함되어 있습니다. 작업 링크를 활용해 한 API에서 다른 API로 옵션을 복사하는 대신 워크플로우를 확장하세요.
테스트 연결을 준비하세요
기존 SQL Server 테스트 데이터베이스와 연결하여 로컬 임시 테이블을 만들 수 있는 로그인 계정을 사용하세요. 예시들은 SQL 인증을 사용합니다. 지원되는 인증 옵션과 비밀번호 없는 Azure 온보딩에 대해서는 서버 연결(Connecting to the Server)을 참조하세요.
선택한 확장 프로그램을 불러오고, PHP 프로세스의 환경에서 SQL_SERVER, SQL_DATABASE, SQL_USER, SQL_PASSWORD를 설정하세요.
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 |
|---|---|
| 명시적 트랜잭션 메서드 | 예시에서는 write를 커밋 또는 롤백해야 하는 단위로 취급합니다. SQLSRV는 , , sqlsrv_commit, sqlsrv_rollback를 사용합니다sqlsrv_begin_transaction; PDO는 자체 트랜잭션 메서드를 사용합니다. |
| SQLSRV parameter descriptors | 각 디스크립터는 방향, PHP 타입, SQL 타입을 지정합니다. 참조는 null를 포함하는 변수를 비롯한 입력 변수를 유지합니다. |
PDO bindValue |
문장은 명시적인 PDO 매개변수 타입을 가진 각 현재 값을 받습니다.
bindParam 이후 실행에서 변수를 참조로 읽어야 할 때 적합합니다. |
| UTF-8 플러스 nvarchar | PHP는 UTF-8 바이트를 제공하며, 확장 프로그램과 ODBC 드라이버가 SQL 유니코드 열용으로 이를 변환합니다. 가져온 텍스트는 원래 PHP 문자열 바이트와 맞아야 합니다. |
| PDO를 위한 명시적 수치 가져오기 | 결과 비교는 PHP 정수를 기대합니다. PDO_SQLSRV 속성이 없으면, 이 SQL 정수 결과는 드라이버의 기본 문자열 표현을 사용합니다. |
| 이름 있는 페치 키 |
SQLSRV_FETCH_ASSOC 그리고 PDO::FETCH_ASSOC 열명으로 인덱싱된 배열을 반환합니다. API 상수는 상호 대체할 수 없습니다. |
PDO::exec 테이블 설정용 |
테이블 생성 문장을 직접 실행합니다. 이 방법은 서버 준비 문장 범위 내에서 임시 테이블 생성을 방지합니다. |
| 정방향 전용 결과 | 프로그램들은 각 행을 한 번씩 읽습니다. 버퍼링 결과를 요청하거나 SELECT 행 수를 가져오기 성공 증거로 사용하지 않습니다. |
SQLSRV는 배열 페치가 끝에 도달하면 false을 반환하고, 페치 오류가 발생하면 null을 반환합니다. PDO의 fetch는 마지막에 false를 반환합니다; 예외 모드가 설정되어 있으면 데이터베이스 fetch 오류를 발생시킵니다. 두 API 모두 필드 자체에 OptionalCount PHP null가 포함되어 있습니다. 그 필드 값은 결과 종료 마커가 아닙니다.
쓰기 작업은 검증 쿼리 전에 커밋됩니다. 나중에 읽는 데 실패하면, 이전에 커밋된 쓰기를 롤백할 수 없습니다. 이 예시에서 해당 행은 정리가 사라지는 임시 테이블에만 속합니다. 영구 테이블이 있는 애플리케이션에서는 실패를 정확히 보고하고, 불확실한 커밋 결과 후에 무작정 인서트를 반복하지 마세요.
고장 및 정리 처리
catch 블록은 활성 트랜잭션을 롤백하고 실패를 다시 발생시킵니다. 블록은 finally 문장을 해제하고, 임시 테이블을 드롭하며, 연결을 해제합니다. 정리는 성공 경로와 실패 경로 사이에서 진행됩니다. 롤백이나 테이블 정리도 실패하면, 프로그램은 원래 예외를 이전 예외로 보존합니다.
명령줄 연습에서는 잡히지 않은 예외가 0이 아닌 종료 코드와 오류 메시지와 함께 PHP를 중단시킵니다. 웹 애플리케이션에서는 요청을 처리하는 코드에서 잡으세요. 진단 기록을 애플리케이션 로그에 기록하기 전에 민감한 값을 제거하고 적절한 오류 응답을 반환하세요. 연결 정보, 자격 증명, 데이터베이스 오류를 브라우저에 노출하지 마세요.
| Failure | 확인할 사항 |
|---|---|
| 확장 기능이나 드라이버가 누락되었습니다 | PHP 프로세스가 의도된 확장 기능을 해당 런타임과 구성으로 로드하는지 확인합니다. 작동하는 명령줄 PHP 구성이 웹 서버가 그것을 사용한다는 증거는 아닙니다. |
| 연결 또는 인증서 오류 | 쿼리 코드를 변경하기 전에 엔드포인트, 인증, 권한, 클라이언트 인증서 신뢰도를 확인하세요. 인증서 검증을 프로덕션 해결책으로 비활성화하지 마세요. |
| 임시 테이블을 찾지 못했습니다 | 셋업, 쓰기, 읽기를 한 연결로 유지하세요. PDO 설정의 경우, 기본 준비된 exec 호출로 대체하지 말고 시연된 query 경로를 유지하세요. |
| 예상치 못한 값 또는 PHP 타입 | fetch 속성과 매개변수 디스크립터를 확인하세요. 엄격한 주장을 느슨한 비교로 대체하여 차이를 숨기지 마세요. |
| 쓰기 또는 롤백 실패 | SQLSRV의 오류를 즉시 캡처하거나 PDO 예외를 캡처하세요. 롤백 실패도 보고하세요. 롤백이 실패한 경우 쓰기가 되돌려졌다고 주장하지 마세요. |
| 연결 끊김 후 정리 실패 | 프로그램이 서버 쪽 드롭을 확인하지 못합니다. PHP 핸들을 해제한다고 해서 기본 풀 물리적 연결이 즉시 닫혔다는 증거는 아닙니다. |
PHP 확장 기능은 바인딩 및 페치 API를 제공합니다. ODBC는 서버와 통신하며 연결 풀링을 관리합니다. SQL Server는 임시 테이블 범위, 트랜잭션, 제약 조건, 커밋된 데이터를 제어합니다. 정리를 위해 물리적 연결이 닫히는 것에 의존하지 말고, 테이블을 명시적으로 삭제하세요.
운동 후에는 삭제 workflow-sqlsrv.php 하고 workflow-pdo.php 더 이상 필요하지 않다면 테스트 프로세스를 마치면 자격 증명 환경 변수를 삭제하세요. 두 프로그램 모두 제거할 영구적인 테이블을 남기지 않습니다.
이 워크스루는 SQL Server를 대상으로 합니다. Azure SQL Database, Azure SQL Managed Instance, Azure Synapse Analytics, Fabric의 SQL 데이터베이스, Warehouse in Fabric의 경우, 먼저 대상의 인증과 T-SQL 지원을 확인하세요. 두 Fabric 제품 모두 Microsoft Entra ID 인증이 필요하며, Warehouse는 다중 활성 결과 세트(MARS)를 비활성화해야 합니다. 이 SQL Server 프로그램들은 서비스별 트랜잭션이나 임시 테이블 지원을 제공하지 않습니다.
다음 작업 선택
필요한 동작에 대해 문서를 통해 워크플로우를 확장하세요.
| 작업 | Guidance |
|---|---|
| 인증을 선택하거나 연결을 관리하세요 | 서버에 접속 중입니다. |
| 다른 값으로 준비된 문을 재사용하세요 | 쿼리 실행 함수를 비교하세요. |
| PDO 직접 실행 또는 준비된 실행문 실행 중 하나를 선택하세요 | PDO_SQLSRV 드라이버에서 직접 문 실행과 준비된 문 실행 |
| 값을 가져오고 결과 모양을 선택하세요 | 데이터를 불러오는 중. |
| 결과를 스크롤하거나 버퍼에 저장하세요 | 커서 유형 지정 및 행 선택 |
| 애플리케이션 데이터 변경 | 데이터 업데이트 중입니다. |
| PHP 및 SQL 타입 변환 제어 | 데이터 형식 변환. |
| 실패 진단 및 드라이버 로깅 활성화 | 오류 및 경고 처리 및 로그 기록. |
| 민감한 컬럼 보호하기 | SQL Server용 PHP용 Microsoft 드라이버에서 Always Encrypted 사용하기 |
| 프로덕션 애플리케이션 보안 강화 | Microsoft Php용 SQL Server용 드라이버에 대한 보안 고려사항. |