我有以下表格
OrderItem(id, service_item_id, special_service_item_id,...)
在我的OrderItem
类中我指定了关系
class OrderItem extends ActiveRecord
{
...
public function getServiceItems()
{
return $this->hasMany(ServiceItem::className(), ['id' => 'special_item_id']);
}
public function getSpecialItems()
{
return $this->hasMany(SpecialItem::className(), ['id' => 'service_item_id']);
}
...
}
通过这种方式,我可以获得Serviceitems的适当实例:
$order = Order::findOne([123]);
//An Array of ServiceItems
$serviceItems = $order->serviceItems;
//An Array of SpecialItems
$specialItems = $order->specialItems;
但是现在我想扩展我的SpecialItem
,实际上我的SpecialItem
是从ServiceItem
扩展而来的。所有项目都保存在同一个表中,但是当我有一个SpecialItem
时,我必须以另一种方式计算一些值。
当然我可以添加一个额外的列extra_speacial_item_id
和定义一个关系getExtraSpecialItem()
,但我认为这不是一个好方法。(如果我想扩展更多呢?)
然而,我发现在YII/YII2中有一种称为单表继承的机制,在这个答案中,我覆盖了BaseActiveRecord
(源)的instantiate()
方法。在另一个答案中,他们将其用作工厂方法。我应该如何实现它?我的想法是:
class ServiceItem extends ActiveRecord
{
public function instantiate($attributes){
switch($attributes['class_name']){
case SpecialItem::className():
$class = SpecialItem::className();
break;
case ExtraSpecialItem::className():
$class = ExtraSpecialItem::className();
break;
default:
$class = get_class($this);
}
$model = new $class(null);
return $model;
}
}
如果我做对了,我必须在instantiate()
方法中放置ServiceItem的所有新子元素。然后我应该将表修改为:
Order(pk_id, fk_service_item_id, class_name, ...)
现在我可以用两种方式访问我的项目:
只使用方法
getServiceItems()
$order = Order::findOne([123]); //An array of mixed Servicetypes, depending on class_name field $serviceItems = $order->serviceItems;
修改
中的关系OrderItem
class OrderItem extends ActiveRecord { ... public function getServiceItems() { return $this->hasMany(ServiceItem::className(), ['id' => 'special_item_id']) ->where(['class_name' => ServiceItem::className()]); } public function getSpecialItems() { return $this->hasMany(SpecialItem::className(), ['id' => 'service_item_id']) ->where(['class_name' => SpecialItem::className()]); } public function getExtraSpecialItems() { return $this->hasMany(ExtraSpecialItems::className(), ['id' => 'service_item_id']) ->where(['class_name' => ExtraSpecialItems::className()]); } ... }
像上面那样访问项:
$order = Order::findOne([123]); //An Array of ServiceItems $serviceItems = $order->serviceItems; //An Array of SpecialItems $specialItems = $order->specialItems; //An Array of ExtraSpecialItems $extraSpecialItems = $order->extraSpecialItems;
是否有另一种(更好的)方法来获得模型的实例,或者这些是唯一合适的解决方案?覆盖instantiate()
方法的方式是否正确?
另一种方式
order_item
表与item_type
表的MANY_MANY。item_type(id, type,other_stuffs)
。(type = service, special, etc)
连接表order_item_type
(或其他什么)将有order_item_id
和item_type_id
作为主复合键。
class OrderItem extends ActiveRecord
{
public function getOrderItemTypes()
{
return $this->hasMany(OrderItemType::className(), ['order_item_id' => 'id']);
}
public function getServiceItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItemTypes')
->where("type = service");
}
public function getSpecialItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItemTypes')
->where("type = special");
}
}
或者类似
的东西class OrderItem extends ActiveRecord
{
public function getOrderItemTypes()
{
return $this->hasMany(OrderItemType::className(), ['order_item_id' => 'id']);
}
public function getItemTypes($type = null)
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItemTypes')
->filterWhere(['type' => $type]); //not sure if you can do this, but looks cool. if not, i'm sure you get what i mean.
}
}
这种数据库设计的优点是规范化和灵活性。
参考