最近在架構某個專案,初步決定會以特定 Framework 開發;雖然之前摸過一陣子 Symfony,不過並沒有熟到能夠閉著眼睛使用的程度。考量基於 PHP 的各種 MVC Framework 發展已成熟,這次就跳來摸 Code Igniter 了。

與 Symfony 相比,CI 號稱架構比較簡單、輕量化、速度也比較快;CI 使用的 view 非常接近 template engine,例如 smarty 的型式,要從假畫面產生樣版的動作,只要幹過 php web coder 的人應該都很熟悉。Controller 方面兩者相當接近,也都有 route 一類的  URI -> Object/Method 的機制。雖然對於第三個以後的變數預設處理方式不同,還是能透過 route 或函式實作來調整。

然而 CI 所提供的 model (功能) 就有點遜,需要自己手刻。雖然 CI 提供了 database 與 Active Record 可以簡化與資料庫的溝通,不過想到要手動針對每項核心資料建立物件、實作關聯性 (查表與產生新物件)、回寫、檢查,實在是有點煩人。前陣子才手刻 ORM 到想翻桌的我,當然無法忍受這種架構 (純屬個人情緒表現)。相對於 Symfony tutorial 曾摸過的 Doctrine 來說,這樣的  model 實在是太陽春啦。

查了一下,發現 CI 有套 ORM 叫作 http://codeigniter.com/wiki/DataMapper_ORM/,這幾天也稍微給它摸了一下。使用 Data Mapper 時,還是需要手動建立核心物件,但無需篆寫屬性相關內容;Data Mapper 會依照物件名稱,自動查找對應表格,在執行期補入內容。對於關聯性的部份,則以 $has_one 與 $has_many 兩項屬性來指定。表格命名若符合格式 (使用小寫英文,物件之複數型式; join table 則為兩個表格名稱依字母排序,底線相連) 就不用額外設定。對重覆 (例如 Post 的 Author 和 Editor 兩個欄位都是 User) 或者表格內參照 (例如實作 Tree 時的 parent_node_id),都可以用簡單的設定解決。

不過碰到比較複雜的表格,例如想把兩個 join table 擠在一起,以 ENUM 欄位來標示,這就不知道該怎麼弄了:

以購物車系統為例,如果有 itempackage 兩類的商品可供購買,兩者均有 id 但可能重覆;訂單存在 order 裡,每筆訂單可以包含多個 item 與 package,並以 join table items_orders儲存相關資料。那麼這個 join table 有幾種可能的結構:

  1. id, order_id, purchase_type ENUM(‘item’,’package’), purchase_id
    我個人偏好這種型式,因為只要兩者 id 格式相同,這可能是最符合正規化要求,並且沒有贅餘欄位與資料的方式;然而透過 Data Mapper,我還不懂要如何實作
  2. id, order_id, item_id NULL, package_id NULL
    這是範例中的做法,如果有一筆 order 帶有一個 item 加上一個 package,那麼可能會在這個表格中佔兩行,分別有一個  null
  3. 使用兩個 join table
    除了很醜以外,沒有太多缺點;也要實作兩個方法分別提取
  4. 錯開 item / package 的 id
    愚蠢到不行的做法,除非是要最小限度修改舊系統,否則千萬不要使用;會帶來很多繁瑣的限制

撇開這點不談,其實 Data Mapper 已經能很容易的操弄資料間的關聯性。然而因為他的表單資料是在 runtime 生成,因此使用 IDE 編輯 PHP 碼時,沒有辦法對這些欄位提供建議,相當可惜 ˇ ˇ 最近會來摸摸 Doctrine propel 這兩套有名的 ORM,看看他們是否更適合當前需求。