EAV 容器
「實體-屬性-值模型(EAV)是一種實體資料模型,描述該實體屬性(特性、參数)的数量可能十分巨大, 但實際上会被應用在所給實體的数量相對適中。 在数學上,这个模型被稱为稀疏矩陣。EAV 也被稱为物件-屬性-值模型, 直式的資料庫模型以及開放的架構。」
简介
大多数人已经在第一段迷失,所以让我們试著用一个範例來解釋。
當你有一个實體(紀錄在 ORM 中的術語)而它有關聯的屬性做为它的子代時,你通常会使用一个 EAV 容器。 但是,對每一條紀錄來說,这些屬性可以是不同的。 这使得它不可能在實體的資料表中定義这些屬性为行,因为会有太多, 他們大多不会有資料,而且你完全不能处理动態屬性 (因为行需要預先被定義)。
要解決这个「關聯」風格的問題,你会建立一張子表,并使用一對多關係關聯到「實體」的資料表, 其中每个屬性会成为子表中的一筆紀錄。 然而,这種作法的缺點是,为了能夠取得一个指定屬性值,你将不得不循环所有關聯紀錄, 比較屬性行與你尋找的值,如果找到一个符合, 取得該值行的內容。
ORM EAV 容器使用这个相同的實现,但允許你合併實體的屬性, 如此該屬性成为實體紀錄的特性, 從而模擬一个 EAV 實现所需的可變行数。
實现
要在一个模型啟用 EAV 容器,該模型需要一張有屬性及值行的子表, 它需要被定義为一對多或多對多關聯。
让我們建立一个醫院病人的範例,該醫院保留病人的紀錄统計。 这取決於病人的病情,他們不能被預先預测。
class Model_Patient extends \Orm\Model
{
// 此模型的特性列表,在这个例子中简化
protected static $_properties = array(
'id', // 主鍵
);
// 設置 statistics 關聯的通常方法
protected static $_has_many = array(
'statistics' => array(
'key_from' => 'id', // 在此模型中的鍵
'model_to' => 'Model_Statistic', // 關聯模型
'key_to' => 'patient_id', // 在關聯模型中的鍵
'cascade_save' => true, // 在儲存時更新關聯資料表
'cascade_delete' => true, // 在刪除父層時刪除關聯資料
)
);
// 像这樣定義 EAV 容器
protected static $_eav = array(
'statistics' => array( // 我們使用 statistics 關聯來儲存 EAV 資料
'model_to' => 'Model_Statistic', // 關聯模型
'attribute' => 'key', // 在關聯資料表中包含屬性的鍵行
'value' => 'value', // 在關聯資料表中包含值的值行
)
);
}
class Model_Statistic extend \Orm\Model
{
// 此模型的特性列表
protected static $_properties = array(
'id', // 主鍵
'patient_id', // 外鍵
'key', // 屬性行
'value', // 值行
);
// 設置病人關聯的通常方法
protected static $_belongs_to = array(
'patient' => array(
'key_from' => 'patient_id',
'model_to' => 'Model_Patient',
'key_to' => 'id',
'cascade_save' => true,
'cascade_delete' => true,
)
);
}
你可以定義多个 EAV 容器,連結到不同的關聯資料表。如果你这樣做, 請求一个屬性会依他們定義的順序來搜尋整个容器,直到找到一个符合。 正如这僅僅是一个模型屬性的額外定位,模型中处理未定義的特性仍保持不變: 它会拋出一个例外。
範例
使用上面的配置,假設我們有以下的資料集:
// SELECT * FROM patient
+----+-----------+
| id | name |
+----+-----------+
| 1 | MisterIll |
| 2 | MissIll |
+----+-----------+
// SELECT * FROM statistics
+----+------------+---------------+----------------+
| id | patient_id | key | value |
+----+------------+---------------+ ---------------+
| 1 | 1 | Temperature | 38.4 |
| 2 | 1 | Coughing | yes |
| 3 | 1 | Headache | no |
| 4 | 2 | Temperature | 37.9 |
| 5 | 2 | Heartbeat | 98 |
+----+------------+---------------+----------------+
所給的这些資料,你可以像这樣存取:
// 建立一些物件來協作
$mr = Patient::find(1);
$ms = Patient::find(2);
// 现在你可以直接取得屬性
echo $mr->Temperature; // '38.4'
echo $ms->Temperature; // '37.9'
echo $mr->Headache; // 'yes'
echo $ms->Headache; // 拋出一个特性找不到的例外
// 或直接設定他們
$mr->Temperature = '36.9'; // 我們的病人似乎有改善
$mr->save(); // 更新病人紀錄,并且更新 EAV 紀錄
// 你仍可以用古老的方法存取資料
foreach ($mr->statistics as $statistic)
{
// 在这裡對每个统計值做點什么……
}
請注意,當你的模型有一个已定義的 EAV 容器,你可以不用再为模型使用自訂資料屬性, 因为每个新屬性会視为一个新的 EAV 鍵。