使用模型类型根据其表获取另一个表
Posted
技术标签:
【中文标题】使用模型类型根据其表获取另一个表【英文标题】: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
【参考方案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 个查询。
【讨论】:
以上是关于使用模型类型根据其表获取另一个表的主要内容,如果未能解决你的问题,请参考以下文章
如何获取已存储在另一个模型中的名称 ID,并使用名称 ID 在 laravel 的数据透视表数据库中存储值?