使用 PDO 操作 Oracle 時,CLOB(Character Large Object)欄位因體積大、型別特殊,讀寫方式與 VARCHAR2 截然不同。本文說明正確的繫結寫入與資源流讀取方法。
預設 PDO 將參數視為 PARAM_STR 並限制預設長度,直接 execute() 會導致資料截斷或錯誤。
$sql = "INSERT INTO articles (id, content) VALUES (:id, :content)";
$stmt = $pdo->prepare($sql);
$id = 1;
$content = str_repeat('長文本內容...', 10000); // 超過 4000 字元
// 關鍵:第四參數指定字串長度
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':content', $content, PDO::PARAM_STR, strlen($content));
$stmt->execute();
$stmt->bindValue(':content', $content, PDO::PARAM_STR, strlen($content));
PDO 取出 CLOB 欄位時,若非 NULL 會回傳 資源型別,需用 stream_get_contents() 讀取實際字串。
$stmt = $pdo->query("SELECT id, content FROM articles WHERE id = 1");
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// 判斷是否為資源並讀取
if (is_resource($row['content'])) {
$row['content'] = stream_get_contents($row['content']);
}
// 現在 $row['content'] 為純字串,可直接 echo、json_encode 等
echo $row['content'];
function fetchClobRow(PDOStatement $stmt): ?array {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return null;
foreach ($row as $key => $val) {
if (is_resource($val)) {
$row[$key] = stream_get_contents($val);
}
}
return $row;
}
// 使用
$stmt = $pdo->prepare("SELECT * FROM articles WHERE id = ?");
$stmt->execute([1]);
$article = fetchClobRow($stmt);
class ArticleRepository {
private PDO $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function create(string $title, string $content): int {
$sql = "INSERT INTO articles (title, content, created_at)
VALUES (:title, :content, SYSTIMESTAMP)
RETURNING id INTO :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':content', $content, PDO::PARAM_STR, strlen($content));
$stmt->bindParam(':id', $id, PDO::PARAM_INT, 32);
$stmt->execute();
return $id;
}
public function findById(int $id): ?array {
$stmt = $this->pdo->prepare("SELECT id, title, content, created_at FROM articles WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return null;
if (is_resource($row['content'])) {
$row['content'] = stream_get_contents($row['content']);
}
return $row;
}
public function update(int $id, string $title, string $content): bool {
$sql = "UPDATE articles SET title = :title, content = :content WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':title', $title, PDO::PARAM_STR);
$stmt->bindParam(':content', $content, PDO::PARAM_STR, strlen($content));
return $stmt->execute();
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 寫入截斷於 4000 字元 | 未指定 bindParam 長度,Oracle 預設 VARCHAR2 限制 | bindParam(..., PDO::PARAM_STR, strlen($data)) |
| 讀取出現 Resource id #5 | CLOB 回傳為 LOB Locator 資源 | stream_get_contents($resource) |
| NULL 值讀取報錯 | NULL 時非資源,直接為 null | is_resource($val) && $val = stream_get_contents($val) |
| 大量資料記憶體耗盡 | 一次性讀取巨大 CLOB | 分塊讀取 stream_read($resource, 8192) 迴圈處理 |
OCI-Lob::writeTemporary、OCI-Lob::read 底層 OCI8 APIENABLE STORAGE IN ROW 讓小於 4KB 的 CLOB 直接存表內CTXSYS.CONTEXT 全文索引支援搜尋掌握「寫入指定長度、讀取轉字串」兩大核心,即可在 PHP 中順暢操作 Oracle CLOB。