傳統 DAO 充斥樣板代碼:連接、預處理、綁定、執行、映射、關閉。利用 PHP 5.3+ 匿名函數 (Closure) 封裝「執行上下文」,實現連接管理、參數綁定、結果映射的關注點分離,極大簡化業務代碼。
<?php
declare(strict_types=1);
class BaseDao {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
/** 通用查詢執行器
* @param string $sql 含佔位符 SQL
* @param array $params 參數陣列
* @param callable|null $mapper 結果映射 Closure (PDOStatement $stmt): mixed
* @return mixed
*/
protected function query(string $sql, array $params = [], ?callable $mapper = null) {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $mapper ? $mapper($stmt) : $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/** 單行查詢 */
protected function queryOne(string $sql, array $params = [], ?callable $mapper = null) {
return $this->query($sql, $params, $mapper ?? fn($stmt) => $stmt->fetch(PDO::FETCH_ASSOC));
}
/** 寫操作 返回影響行數/最後插入 ID */
protected function execute(string $sql, array $params = []): int {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->rowCount();
}
protected function lastInsertId(): string {
return $this->pdo->lastInsertId();
}
}
<?php
class User {
public function __construct(
public int $id,
public string $name,
public string $email,
public string $createdAt
) {}
}
class UserDao extends BaseDao {
public function findAll(): array {
return $this->query(
'SELECT id, name, email, created_at FROM users ORDER BY id DESC',
[],
fn($stmt) => $stmt->fetchAll(PDO::FETCH_FUNC, function($id, $name, $email, $createdAt) {
return new User($id, $name, $email, $createdAt);
})
);
}
public function findById(int $id): ?User {
return $this->queryOne(
'SELECT id, name, email, created_at FROM users WHERE id = ?',
[$id],
fn($stmt) => $stmt->fetch(PDO::FETCH_FUNC, function($id, $name, $email, $createdAt) {
return new User($id, $name, $email, $createdAt);
}) ?? null
);
}
public function insert(User $user): int {
$this->execute(
'INSERT INTO users (name, email, created_at) VALUES (?, ?, NOW())',
[$user->name, $user->email]
);
return (int)$this->lastInsertId();
}
public function update(User $user): bool {
return $this->execute(
'UPDATE users SET name = ?, email = ? WHERE id = ?',
[$user->name, $user->email, $user->id]
) > 0;
}
public function delete(int $id): bool {
return $this->execute('DELETE FROM users WHERE id = ?', [$id]) > 0;
}
}
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', 'user', 'pass');
$userDao = new UserDao($pdo);
// 查詢列表
$users = $userDao->findAll();
foreach ($users as $u) { echo $u->name . PHP_EOL; }
// 單條查詢
$user = $userDao->findById(1);
if ($user) $user->name = 'Updated';
// 寫操作
$newId = $userDao->insert(new User(0, 'New', 'new@test.com', ''));
$userDao->update($user);
$userDao->delete($newId);
transactional(callable $work): mixed 自動提交/回滾。paginate(string $sql, array $params, int $page, int $perPage, callable $mapper) 返回 [items, total, page, perPage]。query() 內集成 Redis/Memcached,Key 由 SQL + 參數哈希生成。PDO::FETCH_CLASS 直接映射到 readonly class。輕量項目、微服務、性能敏感、團隊偏好 SQL 可控時,BaseDao + Closure 提供零依賴、零學習成本、完全可控的替代方案,代碼量約 ORM 1/10,效能損耗可忽略。