Laravel 预先加载按关系排序

Laravel eager loading sort by relationship

我有一些关系(希望我能正确解释)并且需要根据本质上是远距离关系对输出进行排序。

我有一个枢轴 table,其中包含许多关系的详细信息,包括我要排序的关系。

--User.php

public function players()
{
    return $this->belongsToMany('App\Models\Player', 'league_player_user')->withPivot('position_id');
}

--Player.php

public function position()
{
    return $this->belongsToMany('App\Models\Position', 'league_player_user');
}

我会迫不及待地加载这样的关系;

$user = User::with('players')->where('id', Auth::user()->id)->first();

所以,我想我想做这样的事情(不起作用)。

$user = User::with(['players' => function($q){
    $q->orderBy('position.sort_id', 'asc');
}])->where('id', Auth::user()->id)->first();

关键的 Table 结构看起来有点像这样;

league_player_user.
...league_id
...player_id
...user_id
...position_id

头寸 table 包含 sort_id

希望这些信息足够了,如果需要请索取更多信息。非常感谢。

好的,所以您正在尝试获取单个用户,但他们的玩家(其中用户 bTM 玩家)已经填充并按位置排序(其中 pivot bT 位置)。

在这种情况下,如果不进行修改,您将无法使用 Laravel 的内置关系方法,因为 Laravel 并不是为了处理枢轴上的关系而构建的 table 其他关系。幸运的是,ORM 足够灵活,允许您通过 'manual' 连接来做您想做的事。

所以直接回答你的问题,这是你需要的代码(你真的很接近!):

$user = User::with(['players' => function ($q) {
    $q->join('position', 'league_player_user.position_id', '=', 'position.id')
      ->orderBy('position.sort_id', 'asc');
}])->where('id', Auth::user()->id)->first();

但是,在我看来,这不是很好的代码,原因如下:

  1. 您正在手动获取授权用户(当Auth::user()所以方便时)
  2. 您实际上必须从模型中获取特定于实现的逻辑(枢轴 table 被称为 league_player_user 并将其放置的事实......好吧无论它在哪里(您的控制器?)
  3. 这只会影响这一个查询 - 如果您碰巧通过其他方式获得 User(例如 Auth::user()User::find(1) 或其他方式),您将无法获得玩家正确排序

因此,为了使您的查询更简单,我建议您不要急于加载播放器。因此,您需要预先做的是:

$user = Auth::user();

现在,在关系(User class 中的 players() 方法)上,您进行特殊排序工作:

public function players()
{
    return $this->belongsToMany('App\Models\Player', 'league_player_user')
                ->withPivot('position_id')
                ->join('position', 'league_player_user.position_id', '=', 'position.id')
                ->orderBy('position.sort_id');
}

这样,任何时候您调用 $user->players,您都会得到正确的排序。

我必须补充一点,这样做可能不允许您预先加载玩家,因为 Laravel 的预先加载(即在 ORM 链中使用 ->with())由于Laravel 执行预先加载查询的方式 - 一个用于主查询(即用户),一个用于关系(即玩家),但它执行此查询以获得所有结果,因此无法使用特殊的订购系统可能。你必须看看你是否真的关心 eager loading 玩家。在您上面的情况下(您获得单一授权用户的情况),我认为预加载并不那么重要。


编辑以添加有关预加载的说明:

我建议预先加载可能不起作用的原因是 Laravel 中的预先加载有点像这样:假设您有类别和产品:Category hM ProductProduct bT Category。要获得一个类别,您可以使用 $category = Category::find(1) 和 Laravel 将其转换成一个有点像这样的查询:SELECT * FROM `categories` WHERE `id` = '1'。如果您随后调用 $category->products Laravel 将发出 SELECT * FROM `products` WHERE `category_id` = '1'。这是明智的。但是如果你有下面的代码,那就更糟了:

<?php $categories = Category::all(); ?>

<ul>
    @foreach ($categories as $category)
        <li>Category {{{ $category->name }}} contains {{{ $category->products->count() }}} products.</li>
    @endforeach
</ul>

在那种情况下,您会有以下查询:

SELECT * FROM `categories`;
SELECT * FROM `products` WHERE `category_id` = '1';
SELECT * FROM `products` WHERE `category_id` = '2';
SELECT * FROM `products` WHERE `category_id` = '3';
... as many categories as you had

但是,如果您要将第一行更改为:

<?php $categories = Category::with('products')->get(); ?>

现在您只有两个查询:

SELECT * FROM `categories`;
SELECT * FROM `products` WHERE `category_id` IN ('1', '2', '3', ...);

Laravel 然后,在调用第二个查询后,将根据它知道的您拥有的类别 ID 为您创建各种集合。

然而,这是简单化的关系案例。在你的情况下,products() 方法不仅仅是 return $this->hasMany('Product');,它还包括一个数据透视表和一个手动连接等,并且第二个查询(即急切加载查询)可能不会'无法正确处理和执行该排序。

正如我所说,我不确定它是如何工作的,这对我来说只是一个危险信号。一定要试一试,看看你会得到什么 - 你可能会发现它适合你。