Laravel,在给定键的hasMany关系中雄辩的"兄弟姐妹"(下一个,上一个元素)


Laravel, eloquent `sibling` (next, previous element) in hasMany relation by given key

假设我有一个模型Post,它有许多Comments

我还发现了一个特定的评论:

$comment = Comment::find(12);

接下来,我想找到一个基于updated_at专栏的下一个和上一个评论。

例如:

文章:

╔════╦══════════════╦══════╗
║ ID ║       Name   ║ Smtn ║
╠════╬══════════════╬══════╣
║  1 ║ First Post   ║ 5636 ║
║  2 ║ Second Post  ║  148 ║
╚════╩══════════════╩══════╝

评论:

╔════╦══════════════╦═════════╦════════════╗
║ ID ║   Comment    ║ post_id ║ updated_at ║
╠════╬══════════════╬═════════╣════════════╣
║  1 ║ First Comm   ║    1    ║  123       ║
║  2 ║ Second Post  ║    2    ║  124       ║
║  3 ║ Third Post   ║    1    ║  126       ║
║  4 ║ Fourth Post  ║    2    ║  125       ║
║  5 ║ Fifth Post   ║    1    ║  128       ║
║  6 ║ Sixsth Post  ║    1    ║  127       ║
╚════╩══════════════╩═════════╩════════════╝

我的评论是id为3:的

$comment = Comment::find(3);

如何定义previous()next(),以通过updated_at列获取注释,并获取上一个和下一个注释?在这种情况下:

上一篇:

║  1 ║ First Comm   ║    1    ║  123       ║

下一篇:

║  6 ║ Sixsth Post  ║    1    ║  127       ║

updated_at是数据库中的DATETIME(带时区(字段。我懒得写确切的代码。

第一个问题

按照这里的答案做会得到正确的结果,但不建议这样做。

代码将通过PHP数组操作获得与帖子相关的所有注释、应用顺序和位置,然后给您过滤后的注释。事实上,在->comments之后,一切都是PHP。

如果根据您的硬件,评论开始以数百到数千甚至更多的数量出现,这将是非常糟糕的。

为了应对这种情况,你应该像这样做

$subjectComment = Comment::whereId($commentId)->with('post')->first();
$post = $subjectComment->post; 
$previousComment = $post->comments()
    ->where('updated_at', '<=', $subjectComment->updated_at)
    ->where($subjectComment->getKeyName(), '!=', $subjectComment->getKey())
    ->latest('updated_at')
    ->first();
$nextComment = $post->comments()
    ->where('updated_at', '>', $subjectComment->updated_at)
    ->oldest('updated_at')
    ->first();

注意到区别了吗?不这里有一个简短的解释

使用关系作为$post->comments 等模型的属性

这种语法在某些情况下有一定的优点,在其他情况下有缺点。这就是Eloquent在幕后所做的;

  • 检查关系是否已加载
  • 如果没有,则加载它。这意味着它加载所有关联的模型
  • 如果你有逻辑,根据你正在处理的数据,可能加载也可能不加载这个关系,这就太好了
  • 尽管这非常可怕,但如果您只需要查询将返回的模型的一小部分

使用关系作为$post->comments((等模型的函数

此语法返回一个查询,而不是模型,您可以根据需要对其进行进一步自定义。该查询类似于;

Comment::whereHas('post', function ($q) use ($postId) {
    $instance = new Post;
    $q->where($instance->getQualifiedKeyName(), $postId);
});

第二个问题

这两种说法都没有区别。两者将在相同的时间内完成相同的事情。唯一的区别是可读性。

我对这看起来不太满意,但我看不到更好的方法,这对我来说很有意义。如果你有任何想法,请回复。

可能会有拼写错误,因为我的代码有点复杂,而且我的模型有不同的名称。如果您看到任何错误,请报告。

    //get current comment with id 12
    $comment = Comment::->find(12);
    //get parent post
    $post = $comment->post;
    //get all comments of this post
    $comments = $post->comments->orderBy('updated_at',ASC)->get();
    //some helper variables because I don't know the better way
    $prev = null; // the previous comment
    $next  = null; // the next comment
    $counter=0; //helper counter
    //iterate through all comments
    foreach ($comments as $commentIterated) {
        //if this is my comment increase the counter
        if($commentIterated->id == $comment->id) {
            $counter++;
        } elseif ($counter == 1) { //if counter is increased only once increase it again and save current comment as the next one (since in previous iteration we concluded that that was our comment and hence increased the counter first time)
            $next = $commentIterated;
            $counter++;
        } elseif ($counter == 0){ //if counter is not increased current comment is previous one, but this will be rewritten in next iteration if we don't find our comment again
            $prev = $commentIterated;
        }
        $comments->shift(); // Laravel helper method to remove the current element () from the collection so we are left with rest of collection and current instance is again first object in collection which is the next object in this iteration
    }
    return view('my_view', compact('comment','prev','next'));

专家快速提问:

更换这个

    //get parent post
    $post = $comment->post;
    //get all comments of this post
    $comments = $post->comments->orderBy('updated_at',ASC)->get();

用这个

    //get all comments of this post
    $comments = $comment->post->comments->orderBy('updated_at',ASC)->get();

让事情更快?我真的不确定这种方法链接是如何在后台工作的。有没有一种简单的方法可以在Laravel/PHP中对这些查询进行基准测试?

这应该比您现在所做的更干净。只需选择第一个updated_at低于或高于$comment 的1个相关评论

$previous = $post->comments->orderBy('updated_at', 'DESC')->take(1)->where('updated_at', '<', $comment->updated_at)->first();
$next = $post->comments->orderBy('updated_at', 'ASC')->where('updated_at', '>', $comment->updated_at)->first();