在Yii2中从数据库中多态地查找模型


Polymorphically find model from database in Yii2

我在数据库中有一个表(mysql)。但是这个表存储了几种稍微不同类型的行。类型取决于此表的type列。我有一个表的抽象ActiveRecord类和几个子类,它们为不同类型的同一表的行实现了略微不同的逻辑。现在,我正在为所有类型的行实现更新控制器操作。我得到了该行的id,需要创建一个ActiveRecord实例来表示具有该id的行。但不知何故,我需要根据相应行的类型创建不同子类的实例。

如果同时为我提供一个类型和一个id,我本可以使用工厂来选择相应的子类。但我已经可以在数据库中拥有该类型,并且id提供了足够的信息来从中选择它。但是,如果我先从数据库中选择类型,然后创建相应子类的实例,这意味着要执行两次相同的查询。

我想找到一种从数据库中获取数据的好方法,然后选择一个正确的ActiveRecord子类来为它创建一个实例,而不需要进行过多的查询或需要过多的数据。有办法做到这一点吗?

或者我应该以不同的方式处理这个问题?实际的问题是将几个几乎相同但略有不同的实体存储在一个具有略有不同业务逻辑的表中。

解决这个问题的方法之一被称为"单表继承",由Martin Fowler在这里描述。samdark(Yii2的主要贡献者之一)的食谱中也有一篇关于它在Yii2中实现的好文章,该食谱目前正在编写中,但可以在Github上获得。

我不打算复制整篇文章,但只留下链接也是不够的。以下是一些重要事项:

1)为所有类型的对象(例如汽车)创建通用查询:

namespace app'models;
use yii'db'ActiveQuery;
class CarQuery extends ActiveQuery {
    public $type;
    public function prepare($builder)
    {
        if ($this->type !== null) {
            $this->andWhere(['type' => $this->type]);
        }
        return parent::prepare($builder);
    }
}

2)为每种类型创建单独的模型(从通用模型汽车扩展而来):

跑车:

namespace app'models;
class SportCar extends Car
{
    const TYPE = 'sport';
    public static function find()
    {
        return new CarQuery(get_called_class(), ['type' => self::TYPE]);
    }
    public function beforeSave($insert)
    {
        $this->type = self::TYPE;
        return parent::beforeSave($insert);
    }
}

重型汽车:

namespace app'models;
class HeavyCar extends Car
{
    const TYPE = 'heavy';
    public static function find()
    {
        return new CarQuery(get_called_class(), ['type' => self::TYPE]);
    }
    public function beforeSave($insert)
    {
        $this->type = self::TYPE;
        return parent::beforeSave($insert);
    }
}

3)覆盖Car模型中的instantiate()方法以返回正确的车辆类型:

public static function instantiate($row)
{
    switch ($row['type']) {
        case SportCar::TYPE:
            return new SportCar();
        case HeavyCar::TYPE:
            return new HeavyCar();
        default:
           return new self;
    }
}

然后你可以单独使用任何类型的汽车作为常规车型。