Laravel嵌套关系急切加载尊重祖先ID

xoshrz7s  于 2022-11-18  发布在  其他
关注(0)|答案(2)|浏览(123)

bounty将在3天后过期。回答此问题可获得+100声望奖励。abr希望吸引更多人关注此问题。

假设我有一个名为Research的模型,每个研究都属于许多Location模型,每个Location模型也属于许多Contact模型,但是,每个Contact也与Research相关。

class Research extends Model {
   protected $table = 'researches';

   public function locations()
   {
      return BelongsToMany( Location::class, 'research_locations_list', 'research_id', 'location_id' );
   }
}

class Location extends Model {
   protected $table = 'locations';

   public function researches()
   {
      return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
   }

   public function contacts()
   {
      return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
   }
}

class Contact extends Model {
   protected $table = 'contacts';

   public function locations()
   {
      return BelongsToMany( Location::class, 'location_contacts_list', 'location_id', 'contact_id' );
   }
}

researches表格:

+----+------------+
| id |  research  |
+----+------------+
|  1 | Research 1 |
|  2 | Research 2 |
+----+------------+

locations表格:

+----+---------------+
| id |   location    |
+----+---------------+
|  1 | United States |
|  2 | Great Britain |
|  3 | Germany       |
+----+---------------+

contacts表格:

+----+---------+
| id | contact |
+----+---------+
|  1 | Jack    |
|  2 | John    |
|  3 | Hanz    |
+----+---------+

research_locations_list表格:

+----+-------------+-------------+
| id | research_id | location_id |
+----+-------------+-------------+
|  1 |           1 |           1 |
|  2 |           1 |           2 |
|  3 |           2 |           2 |
|  4 |           2 |           3 |
+----+-------------+-------------+

因此,研究1在美国和英国进行,研究2在英国和德国进行
location_contacts_list表格:

+----+-------------+------------+-------------+
| id | location_id | contact_id | research_id |
+----+-------------+------------+-------------+
|  1 |           1 |          1 |           1 |
|  2 |           1 |          2 |           1 |
|  3 |           2 |          1 |           2 |
|  4 |           3 |          3 |           2 |
+----+-------------+------------+-------------+

研究1应将Jack和John作为在美国的联系人,而在其他地方没有联系人;
研究2应该让John作为英国的联系人,让Hanz作为德国的联系人;
现在,使用延迟加载可以实现:

$researches = Research::all();

foreach( $researches as $research )
{
    foreach( $research->locations as $location )
    {
        $contacts = $location->contacts()->wherePivot( 'research_id', $research->id )->get();
        // Will return John and Jack in United States for Research 1 and John in Great Britain and Hanz in Germany for Research 2
    }
}

现在,问题是:我如何通过快速加载实现这一点?

$researches = Research::with( 'locations.contacts' )->all();

foreach( $researches as $research )
{
    foreach( $research->locations as $location )
    {
        $contacts = $location->contacts;
        // Will return John and Jack in United States, John in Great Britain ( which is not supposed to happen ) for Research 1 and John in Great Britain and Hanz in Germany for Research 2
    }
}

也许我可以指示以某种方式为联系人尊重祖先id?像:

$research = Research::with( 'locations.contacts' )->where( 'researches.id = location_contacts_list.research_id' )->all();

更新

最接近解决这个问题的方法是修改Location模型,如下所示:

class Location extends Model {
   protected $table = 'locations';

   public function researches()
   {
      return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
   }

   public function contacts()
   {
      return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
   }

   // Modify contacts attribute getter
   public function getContactsAttribute()
   {
      $contacts = $this->contacts();
      
      if( !empty( $this->pivot->research_id ) )
      {
         $contacts = $contacts->wherePivot( 'research_id', $this->pivot->research_id );
      }
      
      return $contacts->get();
   }
}

但看起来有点脏...

mjqavswn

mjqavswn1#

如果我没猜错的话,你需要在with语句中添加一些条件。如果你想使用有说服力的语法,你可以这样做:

$research = Research::with(['YOUR RELATION' => function ($query) {
    $query->where('YOUR COLUMN', 'EQUALS TO SOMETHING');
}])->get();

请记住,由于在with中使用了嵌套关系,如locations.contacts,查询中的where函数将只筛选最后一个模型(在本例中为contacts)。如果要基于某些条件同时筛选位置和联系人,则必须编写类似以下内容的代码(仅作为示例):

$research = Research::with(['locations' => function ($query) {
    $query->where('id', 1)->with(['contacts' => function ($query) {
        $query->where('name', 'Tim');
    }]);
})->get();

不过,为了实现这一点,您还需要创建与数据透视表的关系(如果您还想在条件中使用它)。否则,您必须使用不同的语法,即使用联接。请查看www.example.com上查询构建器文档中的此页面https://laravel.com/docs/9.x/queries#main-content

s4n0splo

s4n0splo2#

在您的解决方案中,您遇到了N+1查询问题。我可以建议以下解决方案:

class Research extends Model
{
    protected $table = 'researches';

    public function locations(): BelongsToMany
    {
        return $this->belongsToMany(Location::class, 'research_locations_list');
    }

    public function contacts(): BelongsToMany
    {
        return $this->belongsToMany(Contact::class, 'location_contacts_list')
            ->withPivot('location_id');
    }

    public function contactsByLocationAttribute(int $locationId): Collection
    {
        return $this->contacts
            ->filter(static function ($contact) use ($locationId) {
                return $contact->pivot->location_id === $locationId;
            });
    }
}

$researches = Research::with(['locations', 'contacts'])->get();
foreach ($researches as $research) {
    foreach ($research->locations as $location) {
        $contacts = $research->contactsByLocation($location->id);
    }
}

这里总是只有3个对数据库的查询。并且只有必要的模型会被加载

相关问题