<?php
// Database.php - 單例連線管理
class Database {
private static ?PDO $instance = null;
public static function getInstance(): PDO {
if (self::$instance === null) {
$config = require __DIR__ . '/config/db.php';
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
self::$instance = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => false,
]);
}
return self::$instance;
}
// 禁止複製/序列化
private function __construct() {}
private function __clone() {}
public function __wakeup() {}
}
// Repository 範例
class UserRepository {
public function findById(int $id): ?array {
$stmt = Database::getInstance()->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetch() ?: null;
}
public function findByEmail(string $email): ?array {
$stmt = Database::getInstance()->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
return $stmt->fetch() ?: null;
}
public function create(array $data): int {
$pdo = Database::getInstance();
$sql = 'INSERT INTO users (email, name, password_hash, created_at) VALUES (?, ?, ?, NOW())';
$stmt = $pdo->prepare($sql);
$stmt->execute([$data['email'], $data['name'], $data['password_hash']]);
return (int)$pdo->lastInsertId();
}
}
# composer require doctrine/dbal
<?php
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
$config = new Configuration();
$conn = DriverManager::getConnection([
'driver' => 'pdo_mysql',
'host' => '127.0.0.1',
'dbname' => 'my_db',
'user' => 'user',
'password' => 'pass',
'charset' => 'utf8mb4',
], $config);
$qb = $conn->createQueryBuilder();
// 查詢
$users = $qb->select('id', 'name', 'email')
->from('users')
->where('status = :status')
->andWhere('created_at > :date')
->setParameter('status', 'active')
->setParameter('date', '2024-01-01')
->orderBy('created_at', 'DESC')
->setMaxResults(20)
->fetchAllAssociative();
// 插入
$qb->insert('users')
->values([
'email' => '?', 'name' => '?', 'password_hash' => '?', 'created_at' => 'NOW()'
])
->setParameter(0, 'test@example.com')
->setParameter(1, 'Test User')
->setParameter(2, password_hash('secret', PASSWORD_DEFAULT))
->executeStatement();
// 更新
$qb->update('users')
->set('last_login', 'NOW()')
->where('id = ?')
->setParameter(0, 123)
->executeStatement();
// 交易
$conn->transactional(function($conn) {
$qb = $conn->createQueryBuilder();
$qb->insert('orders')->values([...])->executeStatement();
$qb->update('users')->set('balance', 'balance - ?')->where('id = ?')->executeStatement();
});
# composer require doctrine/orm
<?php
// src/Entity/User.php
#[Entity]
#[Table(name: 'users')]
class User {
#[Id, Column(type: 'integer'), GeneratedValue]
private int $id;
#[Column(type: 'string', length: 180, unique: true)]
private string $email;
#[Column(type: 'string', length: 100)]
private string $name;
#[Column(type: 'string', length: 255)]
private string $passwordHash;
#[Column(type: 'datetime_immutable')]
private DateTimeImmutable $createdAt;
#[OneToMany(targetEntity: Order::class, mappedBy: 'user')]
private Collection $orders;
// Getters/Setters / 方法
public function getId(): int { return $this->id; }
public function getEmail(): string { return $this->email; }
public function addOrder(Order $order): void { $this->orders->add($order); $order->setUser($this); }
public function getOrders(): Collection { return $this->orders; }
}
// Repository
class UserRepository extends ServiceEntityRepository {
public function findActiveUsers(int $limit = 20): array {
return $this->createQueryBuilder('u')
->where('u.status = :status')
->setParameter('status', 'active')
->orderBy('u.createdAt', 'DESC')
->setMaxResults($limit)
->getQuery()
->getResult();
}
}
// 使用 (在 Controller/Service 中)
$user = $entityManager->getRepository(User::class)->find(123);
$newUser = new User();
$newUser->setEmail('new@example.com');
$newUser->setName('New User');
$newUser->setPasswordHash(password_hash('secret', PASSWORD_DEFAULT));
$entityManager->persist($newUser);
$entityManager->flush(); // 同步到資料庫
| PHPLib DB_Sql (2000 年代) | 現代對應 |
|---|---|
$db = new DB_Sql; | $pdo = new PDO(...) / $conn = DriverManager::getConnection(...) |
$db->connect(...) | DSN 字串包含連線資訊 |
$db->query("SELECT ...") | $stmt->execute() / $qb->select(...)->executeQuery() |
$db->next_record() | foreach ($stmt as $row) / $qb->fetchAllAssociative() |
$db->f("ID") | $row['id'] / $entity->getId() |
$db->p("Name") | echo htmlspecialchars($row['name']) (View 層責任) |
$db->free() | 自動 GC / $stmt = null |
| 模板分離 (PHPLib Template) | Twig / Latte / Blade 現代模板引擎 |
| 專案特徵 | 建議層級 | 理由 |
|---|---|---|
| 簡單 CRUD、團隊熟悉 SQL、追求效能、無複雜領域邏輯 | PDO + 簡易 Repository | 零依賴、完全控制 SQL、效能最佳 |
| 動態查詢條件多、需跨資料庫支援、團隊偏好流式介面 | Doctrine DBAL (Query Builder) | 類 SQL API、參數化自動化、支援多平台 |
| 豐富領域模型、複雜關聯、繼承、值物件、需要 Unit of Work | Doctrine ORM / Eloquent (Laravel) | 物件導向、自動變更追蹤、關聯導航 |
| 微服務、高效能、CQRS/Event Sourcing | 自訂 Repository + PDO / 專用驅動 | 完全控制、避免 ORM 開銷 |
.env / config/packages/doctrine.yamlPDO::ATTR_EMULATE_PREPARES => false、strict_types=1join/fetch、batchSize、EXTRA_LAZY 集合doctrine/migrations、phinx、laravel migrate 版本化 SchemaPHP 資料庫抽象層已從 PHPLib 時代的 DB_Sql 演進為成熟的三層生態:**PDO (核心)** → **Doctrine DBAL (Query Builder)** → **Doctrine ORM / Eloquent (全功能 ORM)**。選型原則:**從簡單開始,隨複雜度成長而升級**。現代專案啟動建議:
核心不變:**參數化查詢防注入、連線設定外部化、交易邊界清晰、Schema 版本化遷移**。