首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP ORM 框架全景與手寫輕量 ORM 核心實作

     編輯:PHP 相關知識

ORM 核心價值

Object-Relational Mapping (ORM) 解決物件導向模型與關聯式資料庫的阻抗失配:

  • 物件圖:身分識別、繼承、多型、導航屬性、封裝
  • 關聯表格:主鍵/外鍵、正規化、集合運算、無封裝
  • ORM 提供:映射中繼資料 → 自動 SQL 生成 → 物件實例化 → 變更追蹤 → 批次持久化

主流 PHP ORM 框架對比

框架特色學習曲線效能適用場景
Doctrine ORM功能最完整、DQL 查詢語言、事件系統、遷移工具、快取整合陡峭中等 (大量物件較慢)大型企業應用、長期維護專案
Eloquent (Laravel)Active Record、語法優雅、關聯定義簡潔、集合操作強大平緩中等Laravel 專案、中小型應用
PropelXML Schema 定義、代碼生成、查詢建構器、較老牌中等中等傳統專案、偏好代碼生成
RedBeanPHP零配置、動態建表、On-the-fly、極簡 API極低較慢 (動態特性)快速原型、小專案、開發階段
Cycle ORMData Mapper、嚴格型別、PHP 8.1+、效能導向、無反射中等高 (編譯期優化)現代高效能應用
Atlas.OrmMapper 模式、無狀態、嚴格分離、PSR 相容中等高領域驅動設計 (DDD) 專案

手寫輕量 ORM 核心實作 (教學級)

<?php
// 核心介面
interface EntityInterface {
    public function getId();
    public function setId($id);
}

// 基礎實體類別
abstract class Entity implements EntityInterface {
    protected $id;
    protected $data = [];
    protected $originalData = [];
    protected $dirty = false;
    
    public function getId() { return $this->id; }
    public function setId($id) { $this->id = $id; }
    
    public function __get($name) { return $this->data[$name] ?? null; }
    public function __set($name, $value) { 
        $this->data[$name] = $value; 
        $this->dirty = true;
    }
    public function __isset($name) { return isset($this->data[$name]); }
    
    public function toArray() { return $this->data; }
    public function getDirtyData() { 
        return array_diff_assoc($this->data, $this->originalData); 
    }
    public function markClean() { 
        $this->originalData = $this->data; 
        $this->dirty = false; 
    }
}

// 映射定義 (可改用屬性/註解)
class EntityMap {
    public string $table;
    public string $primaryKey = 'id';
    public array $columns = [];  // ['property' => 'column_name']
    public array $relations = [];  // 關聯定義
}

// 實體管理器 (核心)
class EntityManager {
    private PDO $pdo;
    private array $maps = [];
    private array $identityMap = [];  // 單一實體實例快取
    
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }
    
    public function map(string $class, EntityMap $map) { $this->maps[$class] = $map; }
    
    // 查詢單一實體
    public function find(string $class, $id) {
        $key = $class . ':' . $id;
        if (isset($this->identityMap[$key])) return $this->identityMap[$key];
        
        $map = $this->maps[$class];
        $sql = "SELECT * FROM {$map->table} WHERE {$map->primaryKey} = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$row) return null;
        
        $entity = new $class();
        $entity->setId($row[$map->primaryKey]);
        foreach ($map->columns as $prop => $col) {
            $entity->$prop = $row[$col] ?? null;
        }
        $entity->markClean();
        $this->identityMap[$key] = $entity;
        return $entity;
    }
    
    // 條件查詢
    public function findBy(string $class, array $criteria, array $orderBy = [], $limit = null, $offset = null) {
        $map = $this->maps[$class];
        $where = [];
        $params = [];
        foreach ($criteria as $prop => $value) {
            $col = $map->columns[$prop] ?? $prop;
            $where[] = "$col = ?";
            $params[] = $value;
        }
        $sql = "SELECT * FROM {$map->table}";
        if ($where) $sql .= " WHERE " . implode(' AND ', $where);
        if ($orderBy) {
            $orderSql = [];
            foreach ($orderBy as $prop => $dir) {
                $col = $map->columns[$prop] ?? $prop;
                $orderSql[] = "$col " . strtoupper($dir);
            }
            $sql .= " ORDER BY " . implode(', ', $orderSql);
        }
        if ($limit) $sql .= " LIMIT $limit";
        if ($offset) $sql .= " OFFSET $offset";
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        $entities = [];
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $entity = new $class();
            $entity->setId($row[$map->primaryKey]);
            foreach ($map->columns as $prop => $col) {
                $entity->$prop = $row[$col] ?? null;
            }
            $entity->markClean();
            $entities[] = $entity;
        }
        return $entities;
    }
    
    // 持久化 (新增/更新)
    public function persist(EntityInterface $entity) {
        $class = get_class($entity);
        $map = $this->maps[$class];
        
        if ($entity->getId() === null) {
            // INSERT
            $data = $entity->toArray();
            unset($data[$map->primaryKey]);  // 假設自增
            $cols = implode(', ', array_keys($data));
            $placeholders = ':' . implode(', :', array_keys($data));
            $sql = "INSERT INTO {$map->table} ($cols) VALUES ($placeholders)";
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($data);
            $entity->setId($this->pdo->lastInsertId());
        } else {
            // UPDATE (僅髒資料)
            $dirty = $entity->getDirtyData();
            if (!$dirty) return;  // 無變更
            $sets = [];
            foreach ($dirty as $prop => $value) {
                $col = $map->columns[$prop] ?? $prop;
                $sets[] = "$col = :$prop";
            }
            $sql = "UPDATE {$map->table} SET " . implode(', ', $sets) . " WHERE {$map->primaryKey} = :id";
            $params = $dirty;
            $params['id'] = $entity->getId();
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
        }
        $entity->markClean();
    }
    
    // 刪除
    public function remove(EntityInterface $entity) {
        $class = get_class($entity);
        $map = $this->maps[$class];
        $sql = "DELETE FROM {$map->table} WHERE {$map->primaryKey} = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$entity->getId()]);
        unset($this->identityMap[$class . ':' . $entity->getId()]);
    }
    
    // 事務支援
    public function transaction(callable $callback) {
        $this->pdo->beginTransaction();
        try {
            $result = $callback($this);
            $this->pdo->commit();
            return $result;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
}

// 使用範例
// 定義實體
class User extends Entity {
    // 屬性自動透過 __get/__set 處理
}

// 設定映射
$em = new EntityManager($pdo);
$em->map(User::class, (new EntityMap())->setTable('users')->setColumns([
    'name' => 'name',
    'email' => 'email',
    'password' => 'password_hash',
    'createdAt' => 'created_at',
]));

// 業務代碼
$user = $em->find(User::class, 1);
$user->name = '李四';
$em->persist($user);  // 自動判斷 INSERT/UPDATE

// 批次查詢
$users = $em->findBy(User::class, ['status' => 1], ['createdAt' => 'DESC'], 20, 0);
?>

關鍵進階功能 (生產級 ORM 必備)

  1. 關聯載入:Eager Loading (JOIN)、Lazy Loading (Proxy)、批次載入 (IN 查詢)
  2. 查詢建構器:流暢介面 `$em->createQueryBuilder()->select()->where()->getResult()`
  3. 單位工作模式:變更集追蹤、拓撲排序持久化順序、樂觀鎖 (version 欄位)
  4. 快取整合:查詢結果快取 (Redis/APCu)、二級快取 (實體快取)
  5. 遷移系統:Schema 差異比較、版本控制、回滾支援
  6. 事件系統:prePersist、postUpdate、preRemove 等生命週期鉤子

效能優化鐵律

  • N+1 問題:關聯查詢必須使用 Eager Loading 或批次載入
  • 物件實例化開銷:大量資料報表用 DTO/陣列替代實體
  • 身分映射:同一請求內同一實體只實例化一次
  • 延遲載入陷阱:迴圈中存取關聯屬性觸發大量查詢
  • 批次操作:大量寫入使用原生 SQL 或批次 INSERT/UPDATE

選型建議

  • 新專案 + Laravel → Eloquent (零成本、生態完整)
  • 新專案 + 非 Laravel / 追求純淨 → Cycle ORM / Atlas.Orm (現代、型別安全)
  • 大型企業應用、複雜領域 → Doctrine (功能最全、社群最大)
  • 快速原型、內部工具 → RedBeanPHP (零配置、極速開發)
  • 學習 ORM 原理 → 手寫輕量版 (本文核心代碼)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved