schema-database - 查詢結果與 PDO~~FETCH_CLASS
我上週整理我幾年前開發的一個具備簡單 Active record 與 ORM 功能的 PHP 資料庫函數庫 schema-database 。我想要為查詢函數加上一個參數,讓編程人員選擇查詢結果的型態為陣列或個體。因為 schema-database 的底層是 PDO ,所以要調整 PDO 汲取方法(fetch)的選項。
在改寫動作中,我注意到 PDO 有一個選項叫 PDO::FETCH_CLASS 。它與 PDO::FETCH_OBJ 的差別在於 PDO::FETCH_OBJ 汲取出的資料型態是標準類別的個體(stdClass),也就是單純的屬性集合體。而 PDO::FETCH_CLASS 汲取出的資料型態則是指定的類別的個體;這個類別可由使用者自己定義。 PDO::FETCH_CLASS 的用途,很明顯可以搭配 ORM 類,讓 PDO 直接幫使用者配置 ORM 實體。
由於我的 schema-database 本身就有提供一個通用的 ORM 類別,叫 Database_Row。自然我就順手加上了這個選項,讓使用者可以取出具有 ORM 能力的資料記錄。但是 PDO::FETCH_CLASS 有一個在文件中沒有提到的奇特行為,讓我一開始寫的程式發生錯誤,困擾了我一個下午的時間。
在我說明這個奇特行為之前,先讓我們試著思考一下同樣的事,我們應該怎麼做?
首先,我們會從資料庫中讀出指定的記錄。接著我們會配置對應的 ORM 個體。最後將記錄的資料內容,填入 ORM 個體的屬性。或者直接把資料內容作為配置 ORM 個體時的建構參數。總之,一個通常的過程是: 一、調用建構方法配置個體,二、填入資料欄位(個體屬性)。 但是 PDO::FETCH_CLASS 奇特之處就在於它的過程是相反的。它是先填入個體屬性,再調用建構方法。很奇特吧。 在 C++/Java 等語言中,根本沒有先填屬性再調建構方法這種事,因為在你調用建構方法之前根本沒東西存在。但是 PHP 並不禁止這種行為。
下列內容就是用 PHP 語言表示上述所說的過程。假設 Book 是一個 ORM 類別。第一段是一般預期的配置過程。第二段則是 PDO::FETCH_CLASS 的配置過程。
眼尖的人,應該不用執行就可以看出 PDO::FETCH_CLASS 的奇特行為會帶來什麼困擾。 通常,建構方法會將個體的所有屬性指派為預設值或是空值。這表示 PDO::FETCH_CLASS 調用建構方法之後,它原先設定的欄位內容就全部重置了。於是接下來我們取出的值就不是我們預期的內容。
這個奇特行為曾經被舉報為 bug 與修正。但後來因為某些理由(但不知道是什麼),又被 PHP 維護團隊還原回去了,但另外增加一個 PDO::FETCH_PROPS_LATE 的調整選項。相關訊息可參考「Bug #49521 - PDO fetchObject sets values before calling constructor」。 PDO::FETCH_PROPS_LATE 選項可以延後設定屬性的行為,使 PDO::FETCH_CLASS 的配置過程改為先調建構方法再設屬性。
延用前一節的 PHP 程式碼,修改為實際 PDO::FETCH_CLASS 與 PDO::FETCH_PROPS_LATE 的用法。
我一開始說這是我在修改 schema-database 函數庫時碰到的問題。那麼用我的 schema-database 配上 PDO::FETCH_CLASS 會如何? 因為我的 schema-database 的 Database_Row 的建構方法總是會初始資料欄位,若是按照 PDO 原先的奇特行為,那麼所有的資料欄位都會被重置,就沒用了。必須搭配 PDO::FETCH_PROPS_LATE 才會得到正確結果。所以我將 schema-database 設計為看到 PDO::FETCH_CLASS 的選項時,就會自己搭配 PDO::FETCH_PROPS_LATE,且將記錄配置為 Database_Row 個體。
前一節的 pdo_fetch_props_late_example.php ,改用 schema-database 的 Database_Query 實作的話,程式碼將會是下列內容:
我在「PHP - Schema-Database」的介紹文章最後,舉了一個 PowerQuery 例子。當時在該範例中,我要自己取出記錄後轉成 Database_Row 個體。現在不必這麼做了。 但實際操作時的效能議題依舊。如果你只需要從查詢結果中取值,我仍建議你取為標準個體(PDO::FETCH_OBJ)或陣列(PDO::FETCH_ASSOC)。
樂多舊回應