Sopheakdey Moeun
Sopheakdey Moeun

Reputation: 121

Laravel One to Many of One to Many relationship

Currently I'm doing a project with:

My code is:

$invoice = Invoice::find($id);
foreach($invoice->items as $item) {
    $item->subItems;
}
return $invoice;

This works well until an Invoice has too many Items and each Items has too many SubItems and PHP return with a timeout. Does anybody have any other better solution?

Upvotes: 2

Views: 1868

Answers (5)

Sapna Bhayal
Sapna Bhayal

Reputation: 802

$invoice = Invoice::with(array('items','items.subItems')->where('id',$id)->get();

Upvotes: 1

Jinal Somaiya
Jinal Somaiya

Reputation: 1971

try this:

$invoice = Invoice::with('items.subItems')->find($id);

and in Invoice model:

public function items() { 
    return $this->hasMany(Item::class, 'foreign_id', 'id')->orderBy('rank');
}

in Items model:

public function subItems() {
    return $this->hasMany(SubItem::class, 'foreign_id', 'id')->where('rank' ,$this->rank);
}

Upvotes: 2

Uroš Anđelić
Uroš Anđelić

Reputation: 1174

First, I think you have a mistake in your code. Instead of $invoice->$items, you probably meant $invoice->items.

The answer to your question could be to do eager load. Instead of:

$invoice = Invoice::find($id);

try:

$invoice = Invoice::with([
'items' => function ($q) {
    $q->with('subItems');
    }
])->find($id);

This way everything will be loaded in one query. You are currently doing count($invoice->items) + 1 queries. This is called the N + 1 problem and it is very common and important to be aware of.

Upvotes: 1

Rings
Rings

Reputation: 421

The relations are lazy loaded, so for each item a new database connection is opened. This takes some time.

You can eager load the relations with: Invoice::with('subitems')->find($id). More about this at: https://laravel.com/docs/5.7/eloquent-relationships#eager-loading.

Upvotes: 1

Barry
Barry

Reputation: 3328

Update the time allowed with set_time_limit for the thread as each item is iterated over:

$invoice = Invoice::find($id);
$allowed_seconds_per_row = 3;
foreach($invoice->$items as $item){
    set_time_limit($allowed_seconds_per_row);
    $item->subItems();
}
return $invoice;

This still means a request will be taking a large amount of time, but it will only take $allowed_seconds_per_row seconds per row iterated over. If one row takes longer than expected it will sill time out.

Other options are to move the process to cli and adjust the max execution time for that to account for the worst case.

Upvotes: 0

Related Questions