EAV 容器 - Orm 套件
「实体-属性-值模型(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 键。