实现单表继承
关系数据库不支持继承。如果我们需要在数据库中存储继承,我们需要通过代码来支持它。这个代码应该是高效的,从而它应该生成尽量少的JOINs。一个常见的解决方法是Matrin Fowler提出的,叫做单表继承。
当我们使用这个模式时,我们在一张表中存储所有的类树数据,并使用这个类型字段来决定模型的每一行。
作为一个例子,我们希望实现如下单表继承:
Car |- SportCar |- FamilyCar
准备
- 按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的应用。
- 创建并设置一个数据库,添加如下表格:
DROP TABLE IF EXISTS 'car';
CREATE TABLE 'car' (
'id' int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
'name' varchar(255) NOT NULL,
'type' varchar(100) NOT NULL,
PRIMARY KEY ('id')
);
INSERT INTO 'car' ('name', 'type')
VALUES ('Ford Focus', 'family'),
('Opel Astra', 'family'),
('Kia Ceed', 'family'),
('Porsche Boxster', 'sport'),
('Ferrari 550', 'sport');
- 使用Gii为
car
表创建一个Car
模型,并未Car
模型生成ActiveQuery。
如何做…
- 添加如下方法和属性到
models/CarQuery.php
:
/**
* @var
*/
public $type;
/**
* @param \yii\db\QueryBuilder $builder
*
* @return \yii\db\Query
*/
public function prepare($builder)
{
if ($this->type !== null) {
$this->andWhere(['type' => $this->type]);
}
return parent::prepare($builder);
}
- 创建
models/SportCar.php
:
<?php
namespace app\models;
use Yii;
/**
* Class SportCar
* @package app\models
*/
class SportCar extends Car
{
const TYPE = 'sport';
/**
* @return CarQuery
*/
public static function find()
{
return new CarQuery(get_called_class(), ['where' =>
['type' => self::TYPE]]);
}
/**
* @param bool $insert
*
* @return bool
*/
public function beforeSave($insert)
{
$this->type = self::TYPE;
return parent::beforeSave($insert);
}
}
- 创建
models/FamilyCar.php
:
<?php
namespace app\models;
use Yii;
/**
* Class FamilyCar
* @package app\models
*/
class FamilyCar extends Car
{
const TYPE = 'family';
/**
* @return CarQuery
*/
public static function find()
{
return new CarQuery(get_called_class(), ['where' =>
['type' => self::TYPE]]);
}
/**
* @param bool $insert
*
* @return bool
*/
public function beforeSave($insert)
{
$this->type = self::TYPE;
return parent::beforeSave($insert);
}
}
- 添加如下方法到
models/Car.php
:
/**
* @param array $row
*
* @return Car|FamilyCar|SportCar
*/
public static function instantiate($row)
{
switch ($row['type']) {
case SportCar::TYPE:
return new SportCar();
case FamilyCar::TYPE:
return new FamilyCar();
default:
return new self;
}
}
- 添加
TestController
:
<?php
namespace app\controllers;
use app\models\Car;
use app\models\FamilyCar;
use Yii;
use yii\helpers\Html;
use yii\web\Controller;
/**
* Class TestController
* @package app\controllers
*/
class TestController extends Controller
{
public function actionIndex()
{
echo Html::tag('h1', 'All cars');
$cars = Car::find()->all();
foreach ($cars as $car) {
// Each car can be of class Car, SportCar or FamilyCar
echo get_class($car).' '.$car->name."<br />";
}
echo Html::tag('h1', 'Family cars');
$familyCars = FamilyCar::find()->all();
foreach($familyCars as $car)
{
// Each car should be FamilyCar
echo get_class($car).' '.$car->name."<br />";
}
}
}
- 运行
test/index
,你将会得到如下输出:
工作原理…
基础模型Car
是一个典型的Yii AR模型,除了他有两个额外的方法。tableName
方法明确声明了模型使用的表名。单对于Car
模型,这没有意义,单对于子模型,它将会返回相同的car表,这就是我们想要的——整个类树用一个表。instantiate方法被用于AR内部,当我们调用方法例如Car::find()->all()
,用于创建一个模型的实例。我们使用一个switch
来创建不同的类。
SportCar
和FamilyCar
模型只是设置了缺省的AR作用域,所以当我们使用SportCar::model()->
方法查询模型时,我们只会得到SportCar
模型。
参考
参考如下地址,了解更多关于单表继承模式,和Yii Active Record实现: