直接進入主題,當一個Job需要加入佇列的時候,經常會用到一個框架自帶的序列化TraitsIlluminate\Queue\SerializesModels
,例如以下程式碼:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Order;
class CloseCheckOutOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function handle()
{
// TODO XXX
}
}
預設情況下執行dispatch(new CloseCheckOutOrder($order))
是不會報錯的
只有當關聯了relation
時,且關聯的子項也關聯了父項,這時候就會有問題了,例如以下程式碼:
$order = Order::with(['order_goods'])->first();
foreach ( $order->order_goods as $order_goods) {
// 這裡如果業務需求,假裝把訂單商品關聯下父訂單
$order_goods->order()->associate($order);
}
// 然後我們這裡呼叫Job的話就會記憶體溢位
dispatch(new CloseCheckOutOrder($order));
檢視了下原始碼,最終定位到了Illuminate\Database\Eloquent\Model
類下面的方法getQueueableRelations
/**
* Get the queueable relationships for the entity.
*
* @return array
*/
public function getQueueableRelations()
{
$relations = [];
foreach ($this->getRelations() as $key => $relation) {
if (! method_exists($this, $key)) {
continue;
}
$relations[] = $key;
if ($relation instanceof QueueableCollection) {
// 就是這裡出的問題,子關聯項如果也關聯了父級的話,會一直迴圈下去
foreach ($relation->getQueueableRelations() as $collectionValue) {
$relations[] = $key.'.'.$collectionValue;
}
}
if ($relation instanceof QueueableEntity) {
// 就是這裡出的問題,子關聯項如果也關聯了父級的話,會一直迴圈下去
foreach ($relation->getQueueableRelations() as $entityKey => $entityValue) {
$relations[] = $key.'.'.$entityValue;
}
}
}
return array_unique($relations);
}
不難看出上面的程式碼,變成無線迴圈了,所以最終導致記憶體溢位
簡單粗暴的方法就是不設定序列化的關係,在Order
模型裡重寫getQueueableRelations
方法,直接放回一個空陣列既。或者把加入佇列需要序列化的模型的關係去掉