nanigans / single-table-inheritance
单表继承特性
Requires
- php: >= 7.2
- illuminate/database: >= 5.2
- illuminate/support: >= 5.2
Requires (Dev)
- mockery/mockery: ~1.0
- orchestra/database: 3.8.*|4.*|5.*|6.*
- orchestra/testbench: 3.8.*|4.*|5.*|6.*
- phpunit/phpunit: ~8.0|~9.0
README
单表继承是Laravel 5.8+ Eloquent模型的一个特性,它允许多个模型存储在同一个数据库表中。我们支持一些关键特性
- 作为特性实现,以便与其他特性兼容,例如Laravel的
SoftDeletingTrait
或出色的Validating,而不需要复杂的Eloquent模型子类。 - 允许任意类层次结构,而不仅仅是两层父子关系。
- 可自定义用于存储模型类型的数据库列名。
- 可自定义存储在数据库中的模型类型字符串。(与强制使用完全限定的模型类名相反。)
- 允许数据库行不映射到已知的模型类型。它们永远不会出现在查询中。
安装
简单地将包添加到您的composer.json
文件中,并运行composer update
。
"nanigans/single-table-inheritance": "~1.0"
或者,转到您的项目目录,其中包含composer.json
文件,并输入
composer require "nanigans/single-table-inheritance:~1.0"
概述
开始使用单表继承特性非常简单。添加约束并在您的模型中添加一些属性。以下是一个完整的示例,展示了具有两个子类Truck
和Car
的Vehicle
超类
use Nanigans\SingleTableInheritance\SingleTableInheritanceTrait; class Vehicle extends Model { use SingleTableInheritanceTrait; protected $table = "vehicles"; protected static $singleTableTypeField = 'type'; protected static $singleTableSubclasses = [Car::class, Truck::class]; } class Car extends Vehicle { protected static $singleTableType = 'car'; } class Truck extends Vehicle { protected static $singleTableType = 'truck'; }
在您的类中需要定义四个必需的属性
定义数据库表
在根模型中将protected
属性$table
设置为定义用于存储所有类别的数据库表。
注意:即使您正在使用根类的默认值(即Vehicle
类的'vehicles'表),这也是必需的,以便子类继承相同的设置,而不是默认使用它们自己的表名。
定义存储类类型的数据库列
在根模型中将protected static
属性$singleTableTypeField
设置为定义用于存储每个类类型的数据库列。
定义子类
在根模型和每个分支模型中定义protected static
属性$singleTableSubclasses
以定义哪些子类属于类层次结构。
定义类类型值
在每个具体类中设置protected static
属性$singleTableType
以定义将存储在$singleTableTypeField
数据库列中的此类的字符串值。
多级类层次结构
类层次结构有很多层级并不少见。通过在每一级声明子类,可以轻松定义该结构。例如,假设您有一个Vehicle超类,有两个子类Bike和MotorVehicle。MotorVehicle反过来有两个子类Car和Truck。您将定义类如下
use Nanigans\SingleTableInheritance\SingleTableInheritanceTrait; class Vehicle extends Model { use SingleTableInheritanceTrait; protected $table = "vehicles"; protected static $singleTableTypeField = 'type'; protected static $singleTableSubclasses = [MotorVehicle::class, Bike::class]; } class MotorVehicle extends Vehicle { protected static $singleTableSubclasses = [Car::class, Truck::class]; } class Car extends MotorVehicle { protected static $singleTableType = 'car'; } class Truck extends MotorVehicle { protected static $singleTableType = 'truck'; } class Bike extends Vehicle { protected static $singleTableType = 'bike'; }
定义哪些属性将被持久化
在Eloquent中,对获取和设置属性非常宽容。没有机制来声明模型支持的属性集合。如果你误用了属性,尝试对不存在列进行插入或更新操作时通常会导致SQL错误。默认情况下,SingleTableInheritanceTrait也是以这种方式运行的。然而,当在单表中存储类层次结构时,通常会有一些数据库列不适用于层次结构中的所有类。Eloquent将这些值存储在这些列中,使得编写错误变得容易。在那里,SingleTableInheritanceTrait允许你定义哪些属性被持久化。持久化属性的集合也继承自父类。
class Vehicle extends Model { protected static $persisted = ['color'] } class MotorVehicle extends Vehicle { protected static $persisted = ['fuel'] }
在上面的例子中,类Vehicle
将持久化属性color
,而类MotorVehicle
将持久化color
和fuel
。
自动持久化属性
为了方便,模型主键和任何日期都会自动添加到持久化属性列表中。
BelongsTo 关联
如果你正在限制持久化属性,并且你的模型有BelongsTo关联,那么你必须包含BelongsTo关系的外键列。例如
class Vehicle extends Model { protected static $persisted = ['color', 'owner_id']; public function owner() { return $this->belongsTo('User', 'owner_id'); } }
不幸的是,没有有效的方法来自动检测BelongsTo外键。
抛出无效属性的异常
默认情况下,SingleTableInheritanceTrait会静默处理无效属性。在保存模型时,它会忽略未持久化的属性,在从构造器查询中填充模型时,它会忽略未持久化的列。然而,你可以通过将$throwInvalidAttributeExceptions
属性设置为true来强制在上述两种情况下遇到无效属性时抛出异常。
/** * Whether the model should throw an InvalidAttributesException if non-persisted * attributes are encountered when saving or hydrating a model. * If not set, it will default to false. * * @var boolean */ protected static $throwInvalidAttributeExceptions = true;
灵感
我们选择了一种非常特定的实现来支持单表继承。然而,其他人已经编写了代码和文章,围绕一种证明有影响力的通用方法。
首先,Mark Smith有一篇出色的文章(虽然已经不存在但可在网络存档中找到)Single Table Inheritance in Laravel 4,其中介绍了查询返回正确类型对象的重要性。其次,Jacopo Beschi编写了Eloquent的Model
扩展,Laravel-Single-Table-Inheritance,它介绍了能够定义每个模型持久化哪些属性的重要性。
特质的使用受到了Eloquent的SoftDeletingTrait
和优秀的Validating Trait的强烈影响。