(LARAVEL) HasManyThrough 关系导致超过 "Allowed memory size"
(LARAVEL) HasManyThrough relation causing to exceed "Allowed memory size"
为简单起见,让我们以 Whosebug's questions page 为例,其中有一个分页的问题列表,每个问题都附有一些标签。
A Tag 模型与 Question 模型存在多对多关系,即
- 一个标签可以分配给多个问题。
- 一个问题可以分配给多个标签。
对于这个关系,我创建了一个名为 QuestionTag(和 table)的关系模型,它与 Tag 和 Question 都有关系。然后我使用 laravel 的 hasManyThrough
关系通过 QuestionTag
模型获取分配给问题的标签列表:
class QuestionTag extends Model
{
public function question()
{
return $this->belongsTo(Question::class, 'question_id', 'id');
}
public function tag()
{
return $this->belongsTo(Tag::class, 'tag_id', 'id');
}
}
class Question extends Model
{
public function tags()
{
return $this->hasManyThrough(Tag::class, QuestionTag::class, 'question_id', 'id', 'id', 'tag_id');
}
}
并且我创建了 QuestionResource 用于 return 问题的预期分页结果:
问题资源
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'subject' => $this->subject,
'body' => $this->body,
'tags' => $this->tags // this will call the has many through relations as expected.
];
}
}
结果
{
"current_page": 1,
"data": [
{
"id": 1,
"subject": "Lorem ipsum dolor sir amet!",
"body": "...",
tags: [
{
"id": 1,
"name": "tag1",
},
// ...and so on
]
},
// ...and so on
],
"first_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
"next_page_url": null,
"path": "http://127.0.0.1:8000/uv1/questions",
"per_page": "15",
"prev_page_url": null,
"to": 5,
"total": 5
}
最后,在索引函数上,我 return 编辑 QuestionController's index function
中的分页问题列表:
public function index(Request $request)
{
$perPage = $request->input('perPage') ?? 15;
// execute the query.
$crawlers = Question::paginate($perPage);
return QuestionResource::collection($crawlers);
}
它 return 是我想要的,但是当我将 per_page 大小增加到 100 或 更多 , 它是 return 错误:
允许的内存大小为 134217728 字节已用尽(已尝试分配 20480 字节)
我发现许多建议增加 php.ini(memory_limit = 2048M
) 中的内存的解决方案,但感觉我们正在通过暴力破解来实现结果。当我继续增加 per_page 大小时,memory_limit 将再次无法达到 return 的某个点。
在 laravel 中是否有任何 最佳方法可以在不增加内存大小的情况下获得与所需输出相同的预期结果(而不是上述错误)?
我使用 Inner Join 来实现这一点,并在 SELECT
语句中使用 MySQL 的 JSON_ARRAYAGG(JSON_OBJECT())
来创建一个连接json 标签字符串,然后使用 php json_decode()
将其转换为 json 数组。有点俗气,但它返回结果很快,我可以在几毫秒内加载数千条记录。
我的 QuestionController 现在看起来像这样:
public function index(Request $request)
{
$perPage = $request->input('perPage') ?? 15;
// execute the query.
$crawlers = Question::query()
->select(['questions.*', DB::raw('JSON_ARRAYAGG(JSON_OBJECT("id", `tags`.`id`, "name", `tags`.`name`)) AS `tags`')])
->join('question_tags', 'questions.id', 'question_tags.question_id')
->join('tags', 'question_tags.tag_id', 'tags.id')
->groupBy('questions.id')
->paginate($perPage);
return QuestionResource::collection($crawlers);
}
然后我从模型中删除了连接关系,并将我的 QuestionResource 更改为:
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'subject' => $this->subject,
'body' => $this->body,
'tags' => json_decode($this->tags ?? '[]', false, 512, JSON_THROW_ON_ERROR), // convert string JSON to array
];
}
}
目前,我已经实施了这种方法,但我仍然愿意接受更好的解决方案。 :)
为简单起见,让我们以 Whosebug's questions page 为例,其中有一个分页的问题列表,每个问题都附有一些标签。
A Tag 模型与 Question 模型存在多对多关系,即
- 一个标签可以分配给多个问题。
- 一个问题可以分配给多个标签。
对于这个关系,我创建了一个名为 QuestionTag(和 table)的关系模型,它与 Tag 和 Question 都有关系。然后我使用 laravel 的 hasManyThrough
关系通过 QuestionTag
模型获取分配给问题的标签列表:
class QuestionTag extends Model
{
public function question()
{
return $this->belongsTo(Question::class, 'question_id', 'id');
}
public function tag()
{
return $this->belongsTo(Tag::class, 'tag_id', 'id');
}
}
class Question extends Model
{
public function tags()
{
return $this->hasManyThrough(Tag::class, QuestionTag::class, 'question_id', 'id', 'id', 'tag_id');
}
}
并且我创建了 QuestionResource 用于 return 问题的预期分页结果:
问题资源
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'subject' => $this->subject,
'body' => $this->body,
'tags' => $this->tags // this will call the has many through relations as expected.
];
}
}
结果
{
"current_page": 1,
"data": [
{
"id": 1,
"subject": "Lorem ipsum dolor sir amet!",
"body": "...",
tags: [
{
"id": 1,
"name": "tag1",
},
// ...and so on
]
},
// ...and so on
],
"first_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
"next_page_url": null,
"path": "http://127.0.0.1:8000/uv1/questions",
"per_page": "15",
"prev_page_url": null,
"to": 5,
"total": 5
}
最后,在索引函数上,我 return 编辑 QuestionController's index function
中的分页问题列表:
public function index(Request $request)
{
$perPage = $request->input('perPage') ?? 15;
// execute the query.
$crawlers = Question::paginate($perPage);
return QuestionResource::collection($crawlers);
}
它 return 是我想要的,但是当我将 per_page 大小增加到 100 或 更多 , 它是 return 错误:
允许的内存大小为 134217728 字节已用尽(已尝试分配 20480 字节)
我发现许多建议增加 php.ini(memory_limit = 2048M
) 中的内存的解决方案,但感觉我们正在通过暴力破解来实现结果。当我继续增加 per_page 大小时,memory_limit 将再次无法达到 return 的某个点。
在 laravel 中是否有任何 最佳方法可以在不增加内存大小的情况下获得与所需输出相同的预期结果(而不是上述错误)?
我使用 Inner Join 来实现这一点,并在 SELECT
语句中使用 MySQL 的 JSON_ARRAYAGG(JSON_OBJECT())
来创建一个连接json 标签字符串,然后使用 php json_decode()
将其转换为 json 数组。有点俗气,但它返回结果很快,我可以在几毫秒内加载数千条记录。
我的 QuestionController 现在看起来像这样:
public function index(Request $request)
{
$perPage = $request->input('perPage') ?? 15;
// execute the query.
$crawlers = Question::query()
->select(['questions.*', DB::raw('JSON_ARRAYAGG(JSON_OBJECT("id", `tags`.`id`, "name", `tags`.`name`)) AS `tags`')])
->join('question_tags', 'questions.id', 'question_tags.question_id')
->join('tags', 'question_tags.tag_id', 'tags.id')
->groupBy('questions.id')
->paginate($perPage);
return QuestionResource::collection($crawlers);
}
然后我从模型中删除了连接关系,并将我的 QuestionResource 更改为:
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'subject' => $this->subject,
'body' => $this->body,
'tags' => json_decode($this->tags ?? '[]', false, 512, JSON_THROW_ON_ERROR), // convert string JSON to array
];
}
}
目前,我已经实施了这种方法,但我仍然愿意接受更好的解决方案。 :)