carsdotcom/single-table-inheritance

单表继承特性

1.0.1 2024-07-02 20:39 UTC

This package is auto-updated.

Last update: 2024-09-02 20:58:42 UTC


README

Build Status Latest Stable Version Total Downloads Latest Unstable Version License

单表继承是一个适用于 Laravel 9+ Eloquent 模型的特性,允许多个模型存储在同一个数据库表中。我们支持一些关键特性

  • 作为特性实现,以便与其他特性如 Laravel 的 SoftDeletingTrait 或优秀的 Validating 协同工作,而不需要复杂的 Eloquent 模型子类。
  • 允许任意类层次结构,不仅仅是两层父子关系。
  • 可自定义用于存储模型类型的数据库列名。
  • 可自定义存储在数据库中模型类型值的字符串。(与强制使用完全限定模型类名相反。)
  • 允许数据库行不映射到已知的模型类型。它们将永远不会在查询中返回。

安装

只需将包添加到您的 composer.json 文件中,并运行 composer update

"carsdotcom/single-table-inheritance": "^1.0.1"

或者进入包含 composer.json 文件的项目的目录,并输入

composer require "carsdotcom/single-table-inheritance:^1.0.1"

对于 9.x 之前的 Laravel,您仍然可以使用 jonspalmer/single-table-inheritance:1.0.0

概述

使用单表继承特性非常简单。添加约束并在您的模型中添加一些属性。以下是一个完整的示例,包括一个具有两个子类 TruckCarVehicle 超类

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 会持久化 colorfuel 属性。

自动持久化属性

为了方便,模型的主体键和任何日期都会自动添加到持久化属性列表中。

BelongsTo 关系

如果你正在限制持久化属性,并且你的模型有 BelongsTo 关系,那么你必须包含 BelongsTo 关系的 foreign key 列。例如

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,它介绍了能够定义每个模型持久化哪些属性的重要性。

对 Traits 的使用受到了 Eloquent 的 SoftDeletingTrait 和优秀的 Validating Trait 的重大影响。