【问题标题】:Use a model type to fetch another table based on its table使用模型类型根据其表获取另一个表
【发布时间】:2021-08-17 14:51:24
【问题描述】:

我有一个Todo,而这个Todo 可以有一个SubTodo。每个SubTodo 可以是以下类型:

  • 文字
  • 下拉菜单
  • 布尔值

这些SubTodo 中的每一个都有与其类型相关的细节,因此我决定将其分离到自己的表格中。数据库表结构如下:

todo
- id

sub_todo
- id
- todo_id
- type [dropdown, text, boolean]

sub_todo_dropdown
- id
- sub_todo_id

sub_todo_text
- id
- sub_todo_id

sub_todo_boolean
- id
- sub_todo_id

SubTodo 需要有一个名为 meta 的关系,它将解析特定于哪种类型的字段。我定义了以下关系:

模型/SubTodo.php

// The SubTodo will have meta information on other tables
// that will be depending on the type of the SubTodo.
// To fetch those meta information's we will
// have this set where will map the
// relationship to its model
const META_MODELS = [
    self::SUB_TODO_TYPE_TEXT => SubTodoText::class,
    self::SUB_TODO_TYPE_BOOLEAN => SubTodoBoolean::class,
    self::SUB_TODO_TYPE_DROPDOWN => SubTodoDropdown::class,
];

public function meta(): HasOne
{
    return $this->hasOne(self::META_MODELS[$this->type], 'sub_todo_id');
}

但是当我尝试加载这种关系时

$subTodos = SubTodo::with('meta')->paginate();

我收到"message": "Undefined index: "。做完之后

public function meta(): HasOne
{
    dd($this->type);
    return $this->hasOne(self::META_MODELS[$this->type], 'sub_todo_id');
}

我收到null。我最好的猜测是模型还没有加载,所以我需要先加载模型,然后调用meta

$subTodos = SubTodo::limit(10)->get()->each(function (SubTodo $subtodo) {
   $subtodo->load('meta');
});

但是这种方法会导致N+1 问题。有什么方法可以实现加载meta 而无需先加载所有模型?这是one to one polymorphic relationship 的好用法吗?

【问题讨论】:

  • 您需要维护三个不同的表还是可以在一个表中完成?如果您的答案是一张表,您可以使用查询范围。
  • 为了更好地维护未来,这需要三个不同的表。例如,dropdown 类型附加了 dropdown,而 text 类型没有附加 dropdown
  • 你是如何设置 $this->type 的?通过任何构造函数或任何 setter 或默认属性值?
  • $this->type 是模型的属性。它直接来自迁移
  • 恐怕您可能在 self::META_MODELS[$this->type] 处遗漏了 $this->type。可能有一种情况,它只适用于 META_MODELS 数组中的三个值中的任何一个,而其他两个值为 null

标签: php laravel


【解决方案1】:

好吧,我认为除了覆盖模型之外别无他法。

  • 1 创建SubTodoBuilder 类:

    <?php
    
    
    namespace App\Builder;
    
    
    use Illuminate\Database\Eloquent\Builder;
    
    class SubTodoBuilder extends Builder
    {
      const RELATIONS = [
         "text" => "SubTodoText",
         "boolean" => "SubTodoBoolean",
         "dropdown" => "SubTodoDropdown"
      ];
    
     /**
      * Eager load the relationships for the models.
      *
      * @param array $models
      * @return array
      */
     public function eagerLoadRelations(array $models): array
     {
         foreach ($this->eagerLoad as $name => $constraints) {
             if ($name === "meta") {
                 $groupedModels = collect($models)->groupBy("type");
                 $models = [];
                 foreach ($groupedModels as $type => $subModels) {
                     $relation = self::RELATIONS[$type];
                     $result = $this->eagerLoadRelation($subModels->all(), $relation, $constraints);
                     $models = array_merge($models, $result);
                 }
             }
             //This part may need some modification
             if (strpos($name, '.') === false && $name !== "meta") {
                 $models = $this->eagerLoadRelation($models, $name, $constraints);
             }
         }
         return $models;
     }
    }
    
  • 2 在SubTodo 模型中:

    <?php
    
     namespace App\Models;
    
     use App\Builder\SubTodoBuilder;
     use Illuminate\Database\Eloquent\Builder;
     use Illuminate\Database\Eloquent\Factories\HasFactory;
     use Illuminate\Database\Eloquent\Model;
     use Illuminate\Database\Eloquent\Relations\HasOne;
    
     class SubTodo extends Model
     {
       use HasFactory;
    
    
       protected $fillable = [
           "todo_id",
           "type"
       ];
    
       public function SubTodoText(): HasOne
       {
           return $this->hasOne(SubTodoText::class);
       }
    
       public function SubTodoDropDown(): HasOne
       {
           return $this->hasOne(SubTodoDropdown::class);
       }
    
       public function SubTodoBoolean(): HasOne
       {
           return $this->hasOne(SubTodoBoolean::class);
       }
    
       public function meta(): HasOne
       {
           return $this->hasOne("meta");
       }
    
       /**
        * Create a new Eloquent query builder for the model.
        *
        * @param  \Illuminate\Database\Query\Builder  $query
        * @return Builder|static
        */
       public function newEloquentBuilder($query)
       {
           return new SubTodoBuilder($query);
       }
    

    }

  • 3 在你的控制器中

    public function index()
    {
       $subTodos=SubTodo::with("meta")->get();
    }
    

现在,如果您有 30 个 SubTodo 需要获取,并且每个 10 个 SubTodo 具有三种类型之一,它将执行 1+3 个查询。

【讨论】:

    猜你喜欢
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 2022-11-17
    • 1970-01-01
    相关资源
    最近更新 更多