利用 PHP 5.3+ **匿名函數(Closure)** 與 use 關鍵字,可將資料庫連線管理、參數綁定、結果集映射等橫切關注點封裝於基底類別 BaseDao,業務 DAO 僅需專注 SQL 與物件映射,大幅減少樣板代碼。本文完整呈現一個可直接落地的 BaseDao 實作與 ExampleDao 使用範例。
executeQuery() 定義連線→參數綁定→執行→回呼處理→關閉連線的標準流程。mysql_close() 在正常/異常路徑皆被呼叫。<?php
define('DB_USER', 'root');
define('DB_PASS', 'root');
define('DB_NAME', 'ahcdb');
define('DB_HOST', 'localhost');
class BaseDao {
protected function getConnection() {
$link = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if (!$link) throw new Exception('Connect failed: ' . mysqli_connect_error());
return $link;
}
protected function bindParams(string &$sql, array $params = []) {
if (!$params) return;
$sql = vsprintf($sql, array_map(function($v) use ($link) {
if (is_int($v)) return (int)$v;
if (is_float($v)) return (float)$v;
if (is_string($v)) return "'" . mysqli_real_escape_string($link, $v) . "'";
return mysqli_real_escape_string($link, (string)$v);
}, $params));
}
// 核心執行模板
protected function executeQuery(string $sql, array $params = [], ?callable $callback = null) {
$link = $this->getConnection();
try {
$this->bindParams($sql, $params);
$result = mysqli_query($link, $sql);
if (!$result) throw new Exception("Query failed: $sql - " . mysqli_error($link));
$return = $callback ? $callback($result, $link) : null;
return $return;
} finally {
mysqli_close($link);
}
}
// 列表查詢:每行映射為物件
protected function getList(string $sql, array $params = [], callable $rowMapper) {
return $this->executeQuery($sql, $params, function($result) use ($rowMapper) {
$list = [];
while ($row = mysqli_fetch_assoc($result)) {
$list[] = $rowMapper($row);
}
return $list;
});
}
// 單筆查詢
protected function getSingle(string $sql, array $params = [], callable $rowMapper) {
return $this->executeQuery($sql, $params, function($result) use ($rowMapper) {
return ($row = mysqli_fetch_assoc($result)) ? $rowMapper($row) : null;
});
}
}
?>
class Example {
public $id, $name;
public function __construct($id, $name) { $this->id=$id; $this->name=$name; }
public function setId($id) { $this->id=$id; }
}
class ExampleDao extends BaseDao {
public function getAll(): array {
return $this->getList("SELECT * FROM nodes", [], fn($row) => new Example($row['id'], $row['name']));
}
public function load(int $id): ?Example {
return $this->getSingle("SELECT * FROM nodes WHERE id = ?", [$id], fn($row) => new Example($row['id'], $row['name']));
}
public function update(Example $ex): bool {
return $this->executeQuery("UPDATE nodes SET name = ? WHERE id = ?", [$ex->name, $ex->id], fn($res) => $res);
}
public function insert(Example &$ex): bool {
return $this->executeQuery("INSERT INTO nodes (name) VALUES (?)", [$ex->name], function($res, $link) use (&$ex) {
$ex->setId(mysqli_insert_id($link));
return $res;
});
}
}
// 使用示範
$dao = new ExampleDao();
$list = $dao->getAll();
$obj = $dao->load(1);
$dao->update($obj);
$dao->insert($newObj = new Example(0, 'New Node'));
| 技術點 | 說明 |
|---|---|
use (&$ex) | 以**引用**捕獲外部變數,使 insert() 回呼能寫回 $ex->id。 |
fn($row) => ... | PHP 7.4+ 箭頭函數,自動捕獲外部變數,語法更簡潔。 |
vsprintf + array_map | 動態綁定參數,支援 %s、%d、%f 等型別佔位符。 |
try/finally | 確保連線關閉,避免洩漏(PHP 5.5+)。 |
| mysqli 取代 mysql | 原範例用已棄用 mysql_*,本文升級為 mysqli_* 並啟用預處理概念。 |
bindParams 改為 mysqli_prepare + bind_param,徹底防 SQL 注入並提升重複執行效能。transaction(callable $work) 方法,自動處理 begin/commit/rollback。paginate($sql, $params, $page, $perPage, $mapper) 自動計算總數、LIMIT/OFFSET。getList/getSingle 加入 Redis/Memcached 快取層。這個 BaseDao 閉包模式以 **< 100 行核心代碼** 解決連線管理、參數綁定、結果映射三大痛點,是中型專案輕量級 ORM 的優秀起點。