Laravel - 渴望加载多态关系的相关模型

Won*_*nka 28 polymorphism polymorphic-associations eager-loading laravel eloquent

我可以在没有任何n + 1问题的情况下急切加载多态关系/模型.但是,如果我尝试访问与多态模型相关的模型,则会出现n + 1问题,而我似乎无法找到修复.以下是在本地查看的确切设置:

1)DB表名/数据

history

历史表

companies

在此输入图像描述

products

在此输入图像描述

services

在此输入图像描述

2)模型

// History
class History extends Eloquent {
    protected $table = 'history';

    public function historable(){
        return $this->morphTo();
    }
}

// Company
class Company extends Eloquent {
    protected $table = 'companies';

    // each company has many products
    public function products() {
        return $this->hasMany('Product');
    }

    // each company has many services
    public function services() {
        return $this->hasMany('Service');
    }
}

// Product
class Product extends Eloquent {
    // each product belongs to a company
    public function company() {
        return $this->belongsTo('Company');
    }

    public function history() {
        return $this->morphMany('History', 'historable');
    }
}

// Service
class Service extends Eloquent {
    // each service belongs to a company
    public function company() {
        return $this->belongsTo('Company');
    }

    public function history() {
        return $this->morphMany('History', 'historable');
    }
}
Run Code Online (Sandbox Code Playgroud)

3)路由

Route::get('/history', function(){
    $histories = History::with('historable')->get();
    return View::make('historyTemplate', compact('histories'));
});
Run Code Online (Sandbox Code Playgroud)

4)只有$ history-> historable-> company-> name记录n + 1的模板,将其注释掉,n + 1消失..但我们需要那个遥远的相关公司名称:

@foreach($histories as $history)
    <p>
        <u>{{ $history->historable->company->name }}</u>
        {{ $history->historable->name }}: {{ $history->historable->status }}
    </p>
@endforeach
{{ dd(DB::getQueryLog()); }}
Run Code Online (Sandbox Code Playgroud)

我需要能够急切地加载公司名称(在单个查询),因为它是多态的关系模型的相关模型ProductService.我已经工作了好几天但找不到解决方案. History::with('historable.company')->get()只是忽略了companyin historable.company.这个问题的有效解决方案是什么?

dam*_*ani 58

解:

如果你添加:

protected $with = ['company']; 
Run Code Online (Sandbox Code Playgroud)

同时ServiceProduct模特.这样,company每次加载Service或加载时都会急切地加载关系Product,包括通过多态关系加载时History.


说明:

这将导致额外的2个查询,一个用于Service一个查询,一个用于Product查询,即每个查询一个查询historable_type.所以你的查询总数 - 不管结果的数量 - n来自m+1(没有急切加载远距离company关系)(m*2)+1,其中m是你的多态关系链接的模型数量.


可选的:

这种方法的缺点是你总是急于加载和模型company上的关系.这可能是也可能不是问题,具体取决于数据的性质.如果这是一个问题,您可以使用此技巧在调用多态关系时自动加载.ServiceProductcompany

将此添加到您的History模型:

public function getHistorableTypeAttribute($value)
{
    if (is_null($value)) return ($value); 
    return ($value.'WithCompany');
}
Run Code Online (Sandbox Code Playgroud)

现在,当你加载historable多态关系,雄辩会寻找类ServiceWithCompanyProductWithCompany,而不是ServiceProduct.然后,创建这些类,并with在其中设置:

ProductWithCompany.php

class ProductWithCompany extends Product {
    protected $table = 'products';
    protected $with = ['company'];
}
Run Code Online (Sandbox Code Playgroud)

ServiceWithCompany.php

class ServiceWithCompany extends Service {
    protected $table = 'services';
    protected $with = ['company'];
}
Run Code Online (Sandbox Code Playgroud)

...最后,您可以protected $with = ['company'];从基础ServiceProduct类中删除.

有点hacky,但它应该工作.


Raz*_*zor 10

你可以分开集合,然后懒得加载每一个:

$histories =  History::with('historable')->get();

$productCollection = new Illuminate\Database\Eloquent\Collection();
$serviceCollection = new Illuminate\Database\Eloquent\Collection();

foreach($histories as $history){
     if($history->historable instanceof Product)
          $productCollection->add($history->historable);
     if($history->historable instanceof Service)
        $serviceCollection->add($history->historable);
}
$productCollection->load('company');
$serviceCollection->load('company');

// then merge the two collection if you like
foreach ($serviceCollection as $service) {
     $productCollection->push($service);
}
$results = $productCollection;
Run Code Online (Sandbox Code Playgroud)

可能它不是最好的解决方案,protected $with = ['company'];按照@damiani的建议添加是一个很好的解决方案,但这取决于您的业务逻辑.


Joã*_*rme 5

拉取请求#13737#13741修复了这个问题。

只需更新您的 Laravel 版本和以下代码

protected $with = [‘likeable.owner’];
Run Code Online (Sandbox Code Playgroud)

将按预期工作。