yiissy001 2015-07-21 15:55:11 4374次浏览 0条评论 3 3 0

以后会看心情不定时翻译github上的yii-cookbook(url: https://github.com/samdark/yii2-cookbook/tree/master/book)
英文水平有限,有错误请一定指出来.

原文地址: https://github.com/samdark/yii2-cookbook/blob/master/book/ar-single-table-inheritance.md

译文:
目前大多数的关系型数据库是不原生支持继承的,这就导致了我们必须手动实现继承的功能.其中一个解决办法就是单表继承(Single table inheritance),这在http://martinfowler.com/eaaCatalog/singleTableInheritance.html 里说的很详细(注: 简单的说单表继承就是用一个表表示父类,每行记录子类的信息,然后多加一个列type表示该子类的类型).
以下实现的是一个简单的car类型的继承关系结构:

Car
|- SportCar
|- HeavyCar

准备
首先我们要建张表,并添加点测试数据,SQL如下:

CREATE TABLE `car` (
    `id` int NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    `type` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
);
  
INSERT INTO car (id, NAME, TYPE) VALUES (1, 'Kamaz', 'heavy'), (2, 'Ferrari', 'sport'), (3, 'BMW', 'city');

然后用GII生成Car Model

实现
我们需要先建个简单的Car的ActiveQuery来实现Car类型的查询条件的添加.

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);
    }
}

然后我们需要为两个子类添加model文件,首先是SportCar.php:

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);
    }
}

然后是HeavyCar.php:

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);
    }
}

最后需要在Car model中重写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;
    }
}

好了,让我们在controller里添加个actionTest试试:

// finding all cars we have
$cars = Car::find()->all();
foreach ($cars as $car) {
    echo "$car->id $car->name " . get_class($car) . "<br />";
}

// finding any sport car
$sportCar = SportCar::find()->limit(1)->one();
echo "$sportCar->id $sportCar->name " . get_class($sportCar) . "<br />";

下面是输出:

1 Kamaz app\models\HeavyCar
2 Ferrari app\models\SportCar
3 BMW app\models\Car
2 Ferrari app\models\SportCar

这说明model根据type列实例化了对应的子类.

工作原理:
SportCar 和 HeavyCar非常相像.他们都继承自Car类并且有两个方法被重写了.在find方法中,我们实例化了一个根据type定制的query class,这是通过重写prepare方法(此方法在生成SQL之前被调用)将"and type=xxxx"条件插入进去实现的.这样SportCar就对应type=sportCar的记录, heavyCar就对应type=heavyCar的记录.重写beforeSave方法则保证了在子类保存时type被正确的设定了.TYPE常量让代码更易懂.
Car model除了instantiate函数之外完全就是GII生成的.这个函数是在数据被从DB中查出后正要初始化类属性时被调用的.它的返回值是未初始化的对象,而它唯一的入参代表一行数据库里的数据(以数组形式).这个函数里是一个简单的switch用来根据type生成我们想要的对象.

觉得很赞
    没有找到数据。
您需要登录后才可以评论。登录 | 立即注册