PHP 搭配 Oracle 開發企業級應用時,**預存程序 (Stored Procedure)** 是封裝業務邏輯、提升效能、強化安全的關鍵利器。本文以完整可運行的程式碼,示範從資料庫端建立 Procedure、到 PHP 端使用 **OCI8 擴展** 進行參數綁定、執行與結果處理的全流程最佳實踐。
CREATE TABLE test_proc (
id NUMBER(16) NOT NULL PRIMARY KEY,
name VARCHAR2(30) NOT NULL
);
INSERT INTO test_proc VALUES (5, 'PHP_book');
COMMIT;
CREATE OR REPLACE PROCEDURE proc_test (
p_id IN OUT NUMBER, -- 輸入查詢條件,亦可回傳異動後的值
p_name OUT VARCHAR2 -- 輸出查詢結果
) AS
BEGIN
SELECT name INTO p_name
FROM test_proc
WHERE id = p_id; -- 此處 p_id 為 5
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_name := 'NOT_FOUND';
WHEN OTHERS THEN
p_name := 'ERROR:' || SQLERRM;
END proc_test;
/
= Procedure 定義)
// ========== 4. 解析 SQL 呼叫語句 ==========
// 語法: BEGIN 程序名(:參數1, :參數2); END;
// 冒號表示位置佔位符,名稱建議與 Procedure 參數名一致
$sql = "BEGIN proc_test(:id, :name); END;";
$stmt = oci_parse($conn, $sql);
if (!$stmt) {
$e = oci_error($conn);
die("Parse failed: " . $e['message']);
}
// ========== 5. 綁定參數 (關鍵步驟) ==========
// oci_bind_by_name(resource $statement, string $param_name, mixed &$variable, int $maxlength = -1, int $type = SQLT_CHR)
// $maxlength: OUT 參數必須指定足夠長度,否則截斷或報錯
oci_bind_by_name($stmt, ':id', $id, 16); // NUMBER(16) -> 長度 16 足夠
oci_bind_by_name($stmt, ':name', $name, 30); // VARCHAR2(30) -> 長度 30
// ========== 6. 執行 ==========
$r = oci_execute($stmt);
if (!$r) {
$e = oci_error($stmt);
die("Execute failed: " . $e['message']);
}
// ========== 7. 取得 OUT 參數值 ==========
// 執行後,$name 變數已被 Oracle 回寫
echo "Name is: $name
\n";
// ========== 8. 清理資源 ==========
oci_free_statement($stmt);
oci_close($conn);
?>
| 環節 | 核心要點 |
|---|---|
| 連線函式 | oci_connect (非持久) vs oci_pconnect (持久,Apache Prefork MPM 下慎用,易超連線數) |
| SQL 語法 | 必須包裹在 BEGIN ... END; 區塊,參數前加冒號 :param |
| 參數模式 | IN: 僅輸入;OUT: 僅輸出 (需初始化變數);IN OUT: 雙向 (需給初始值) |
| 綁定長度 | OUT/IN OUT 參數 **必須指定 $maxlength** >= 定義長度,否則 ORA-06502 數值/字串截斷錯誤 |
| 資料類型對應 | NUMBER → PHP int/float/string;VARCHAR2 → PHP string;DATE → PHP string (需 NLS_DATE_FORMAT) 或 OCI_B_DATE |
| 例外處理 | Procedure 內部 EXCEPTION WHEN OTHERS 回傳錯誤碼/訊息至 OUT 參數,避免中斷 PHP 流程 |
-- DB 端
CREATE OR REPLACE FUNCTION func_get_name(p_id NUMBER) RETURN VARCHAR2 AS
v_name VARCHAR2(30);
BEGIN
SELECT name INTO v_name FROM test_proc WHERE id = p_id;
RETURN v_name;
EXCEPTION WHEN NO_DATA_FOUND THEN RETURN 'NOT_FOUND';
END;
/
-- PHP 端
$sql = "BEGIN :ret := func_get_name(:id); END;";
$stmt = oci_parse($conn, $sql);
$ret = '';
oci_bind_by_name($stmt, ':ret', $ret, 30); // RETURN 值視為 OUT 參數
oci_bind_by_name($stmt, ':id', $id, 16);
oci_execute($stmt);
echo "Function returned: $ret";
oci_bind_by_name 是否漏寫。$maxlength。:ret。oci_bind_by_name,防 SQL 注入。p_code, p_msg OUT 參數,PHP 端統一處理。oci_free_statement + oci_close 必須成對出現,避免洩漏。掌握 OCI8 參數綁定機制,讓 PHP 與 Oracle 預存程序無縫協作,是構建高效、安全、可維護企業級應用的關鍵基石。