【问题标题】:Multi-Table Inheritance in Laravel EloquentLaravel Eloquent 中的多表继承
【发布时间】:2019-11-24 19:18:21
【问题描述】:

对于在 PDF 文档上书写,用户应该能够创建可以在多个Documents 中重复使用的不同“模块”。有一些普通模块 (Module) 具有属性 name, posX, posY 和例如TextModule 具有 Module 的所有属性,但添加了 text, font, color, size。这就是您通常使用继承实现的目标。 我找到了几种使用 Eloquent 构建单表继承的方法,但这会导致数据库中有很多 NULL 值,因为所有 Module 对象都不会有任何 text, font, colorsize。 不幸的是,我没有找到任何 Eloquent 的多表继承文档。

这是我目前所拥有的:

class Module extends Model
{
    protected $fillable = [
        'name', 'posX', 'posY'
    ];

    public function document()
    {
        return $this->belongsTo('App\Document');
    }
}

class TextModule extends Module
{
    protected $fillable = [
        'text', 'font', 'color', 'size'
    ];
}

此外,我的方法是创建两个迁移(因为我需要多表继承)并在 create_modules_table 迁移中包含每个公共属性,而我已将每个“特殊”属性添加到 create_textmodules_table

我希望调用Module::all() 来检索任何类型的模块,所以在这个例子中是ModuleTextModule。对于返回集合中包含的每个对象,应该可以调用obj->document 来检索相应的文档(对于Document::hasMany(Module::class) 关系,反之亦然)。 目前我只在调用Module::all() 时收到所有Module 对象而没有任何错误消息。

我的方法是否走错了路?

【问题讨论】:

    标签: php laravel inheritance eloquent


    【解决方案1】:

    我建议使用嵌套集实现,而不是为模块的每个特殊情况使用单独的表。它主要用于网页上的嵌套类别,但理论上可以用于任何类型的父/子关系。看看下面的Laravel-nestedset 包。

    【讨论】:

    • 我已经使用Laravel-nestedset 设置了一个示例,但它似乎只允许由同一类的元素组成的嵌套集:Argument 1 passed to App\TextModule::appendToNode() must be an instance of App\TextModule, instance of App\Module given
    【解决方案2】:

    您可以使用此页面进行进一步参考:Laravel User Types and Polymorphic Relationships

    【讨论】:

    • 谢谢,这与我想要的非常接近。我在一个新项目中尝试过,与您的链接相比仍然需要一些更改。请参阅我的单独回复显示我的解决方案。
    【解决方案3】:

    如果您不介意将您的数据存储为 json(这样您就知道在那里丢失了什么),我可能会建议一种不同的方法。一个非常基本的例子,有一个field 文本列,可能是(未经测试的代码):

    class Module extends Model
    {
        protected $fillable = [
            'name', 'posX', 'posY', 'field'
        ];
    
        protected $casts = [
            'field' => 'object'
        ];
    
        public function document()
        {
            return $this->belongsTo('App\Document');
        }
    }
    
    class TextModule extends Module
    {
        protected $appends = [
            'text', 'font', 'color', 'size'
        ];
    
        public function getTextAttribute(): string
        {
            return $this->field->text;
        }
    
        public function setTextAttribute(string $value): void
        {
            $field = $this->field;
            $field->text = $value;
            $this->field = $field;
        }
    
        // etc...
    }
    

    显然,通过这种方式,您是在以数据完整性换取灵活性,因此我建议仅在前者远不如后者重要时才建议这样做。例如,我之前在创建 html 电子邮件编写器时使用了这种模式。每次管理层要求新的字段类型时,我都需要几分钟来实现它,而无需创建新的数据库迁移。但是,再一次,这只是因为在这个特定的项目中,我并不真正在意数据完整性。

    【讨论】:

    • 有趣的方法,谢谢!拥有一个存储键值对并以模块 ID 作为外键的“扩展表”不是更简单吗?从性能的角度来看,您知道什么更好吗?加入数据库或使用此隐式json_decode()?
    • 嗯,非常不同的方法。您的方法命中数据库,挖掘 Web 服务器(json 转换)。我不是代码性能方面的专家来区分差异,但乍一看,我会说我的速度更快,只是因为它对数据库的查询要简单得多。
    • 还有一件事。你有没有想过使用组合而不是继承?例如,急切加载会导致您在使用继承时遇到麻烦。我的意思是...$document->hasMany('App\What?').
    【解决方案4】:

    感谢@sss S 关于 Laravel 中多态关系的链接,我终于想出了如何解决我的问题:

    模型

    class Module extends Model {
      public function moduleable() {
        return $this->morphTo();
      }
    }
    
    class TextModule extends Model {
      public function module() {
        return $this->morphOne('App\Module', 'moduleable');
      }
    }
    

    迁移

    Schema::create('modules', function (Blueprint $table) {
      $table->bigIncrements('id');
      $table->float('posX');
      // ... other fields mentioned above
      $table->morphs('moduleable'); // this creates a "moduleable_id" and "moduleable_type" field
      $table->timestamps();
    });
    
    Schema::create('textmodules', function (Blueprint $table) {
      $table->bigIncrements('id');
      // ... only the fields that are exclusive for a TextModule (= not in Module, except "id")
    });
    

    工厂

    $factory->define(TextModule::class, function (Faker $faker) {
        return [
            // ... fill the "exclusive" fields as usual
        ];
    });
    
    $factory->define(Module::class, function (Faker $faker) {
      $moduleables = [
        TextModule::class,
        // ... to be extended
      ];
    
      $moduleableType = $faker->randomElement($moduleables);
      $moduleable = factory($moduleableType)->create();
    
      return [
        // ... the fields exclusive for Module
        // add the foreign key for the created "moduleable" (TextModule)
        'moduleable_id' => $moduleable->id,
        'moduleable_type' => $moduleableType
        ];
    });
    

    控制器

    public function index() {
      $all = \App\Module::whereHasMorph('moduleable', '*')->with('moduleable')->get();
      return response()->json($all);
    }
    

    通配符 * 将显示按照上述步骤配置的任何特定 Module(例如 TextModule、ImageModule)。添加->with('moduleable') 会直接为每个Module 填充“特定”属性。请查看 Laravel 官方文档中的 "Querying Polymorphic Relationships" 部分以获取更多信息。

    输出

    [{
       "id":1,
       "posX":34.47,
       "posY":17.04,
       "moduleable_type":"App\\TextModule",
       "moduleable_id":1,
       "created_at":"2019-12-02 20:08:01",
       "updated_at":"2019-12-02 20:08:01",
       "moduleable":{
          "id":1,
          "font":"Arial",
          "color":"#94d22e",
          "size":12,
          "created_at":"2019-12-02 20:08:00",
          "updated_at":"2019-12-02 20:08:00"
       }
    }]
    

    因为我还没有设法在互联网上找到关于这个场景的综合教程,所以我决定发布我的GitHub repository 来玩玩。

    【讨论】:

    • 抱歉没有意义的评论,但我刚刚意识到您正在研究多态表关系:)
    猜你喜欢
    • 2018-06-16
    • 1970-01-01
    • 1970-01-01
    • 2014-12-28
    • 2016-02-01
    • 2021-10-19
    • 2019-12-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多