获取Laravel中每个用户的最新消息(行)

时间:2016-06-30 10:44:17

标签: php laravel

TL; DR:需要每个发件人的最新消息。

在我的Laravel应用程序中,我有两个表:

用户:

  • ID
  • 名称

消息:

  • ID
  • SENDER_ID
  • recipient_id
  • created_at

当然还有模特。

用户模型:

public function messages() {
    return $this->hasMany('App\Message', 'recipient_id');
}

消息模型:

public function sender() {
    return $this->belongsTo('App\User', 'sender_id');
}

public function recipient() {
    return $this->belongsTo('App\User', 'recipient_id');
}

当用户打开收件箱时,他应该会看到来自任何其他用户的最新消息列表。

所以如果有消息:

id sender_id recipient_id body created_at
1, 2,        1,           hi,  2016-06-20 12:00:00
2, 2,        1,           hi,  2016-06-21 12:00:00
3, 3,        1,           hi,  2016-06-20 12:00:00
4, 3,        1,           hi,  2016-06-21 12:00:00

然后,id为1(recipient_id)的用户应该只能看到ID为2和4的消息。

这是用户模型中的当前解决方案:

return Message::whereIn('id', function($query) {
                $query->selectRaw('max(`id`)')
                ->from('messages')
                ->where('recipient_id', '=', $this->id)
                ->groupBy('sender_id');
            })->select('sender_id', 'body', 'created_at')
            ->orderBy('created_at', 'desc')
            ->get();

这是有效的,但如果有可能实现Laravel方式,我就会徘徊。可能有急切的装载。我的Laravel技能还不够,经过几天的尝试,我没有解决方案。

感谢。

9 个答案:

答案 0 :(得分:5)

this post中获取灵感,最有效的方法就是这样:

DB::table('messages AS m1')
    ->leftjoin('messages AS m2', function($join) {
        $join->on('m1.sender_id', '=', 'm2.sender_id');
        $join->on('m1.id', '<', 'm2.id')
    })->whereNull('m2.id')
    ->select('m1.sender_id', 'm1.body', 'm1.created_at')
    ->orderBy('m1.created_at', 'm1.desc')->get();

虽然它不是Laravel最友好的,但它是基于性能的最佳解决方案,正如上面这个答案所链接的帖子所强调的那样

答案 1 :(得分:2)

为什么不简单地访问messages,就像这样 -

// get the authenticated user
$user = \Auth::user(); 

// find the messages for that user
return User::with('message')->find($user->id)->messages;

答案 2 :(得分:2)

我尝试了一些类似的方法,发现你只需要在created_at找到User的所有消息后立即订购,然后你可以使用Laravel的Collection将它们分组{ {1}}。因此,根据我的理解,以下方法应该有效,即从发件人 s

为您提供用户接收的最后一条消息:

假设您有一个经过身份验证的sender_idUser,在此上下文中与$user receiver receiver_id相同,那么:< / p>

messages

然后遍历集合,并获取第一个:

$messages = $user->messages()
    ->orderBy('created_at', 'desc')
    ->get(['sender_id', 'body', 'created_at'])
    ->groupBy('sender_id'); //this is collections method

你应该得到如下结果:

$result =  new \Illuminate\Database\Eloquent\Collection ();
foreach ($messages as $message){
    $result->push($message->first()); //the first model in the collection is the latest message
}
  PS:请注意,我不能说这可能有多高效,但有一点可以肯定的是,数据库上的查询数量有限。

希望有所帮助:)

更新

我会在尝试时将其分解。 〜

它将属于用户的 Illuminate\Database\Eloquent\Collection {#875 all: [ App\Message {#872 sender_id: "2", body: "hi", created_at: "2016-06-21 12:00:00", }, App\Message {#873 sender_id: "4", body: "hi", created_at: "2016-06-21 12:00:00", }, ] 的所有记录提取到一个集合中(已由created_at排序),然后使用laravel's groupBy()得到一个结果,如该文档中给出的示例。

Laravel's groupBy()

这次我没有转换为Array。相反,它是一个收藏集合。喜欢集合(集合(消息))

然后在每个索引处选择第一个模型。我已经传递给messages方法的参数确保只存在那些字段(即get()。这与mysql groupBy()完全不同,因为它不为每个组返回一行,而是只需按给定的标识符对所有记录进行分组。

另一个更新

后来我发现在生成的有序消息上调用->get(['sender_id', 'body', 'created_at'])方法实际上可以完成这项工作,但在这种情况下,唯一标识符将是unique()。 (参考:Laravel's unique)即:

sender_id

结果是我们不需要$messages = $user->messages() ->orderBy('created_at', 'desc') ->get(['sender_id', 'body', 'created_at']) ->unique('sender_id'); //unique replaces `groupBy` foreach我们在这里得到结果。

另一种避免重复的方法(如果需要在多个地方使用)是使用Laravel's query scope,即在groupBy模型中,我们可以有这样的东西:

Message

然后在控制器中使用:

public function scopeLatestSendersMessages($query) 
{
     return $query->orderBy('created_at', 'desc')
         ->get(['sender_id', 'body', 'created_at'])
         ->unique('sender_id');
}

PS:仍然不确定哪一个比另一个更好。

答案 3 :(得分:1)

这可能是一个解决方案(虽未测试)

User::with([
'messages' => function ($q) {
    $q->select('sender_id', 'body')->groupBy('sender_id')->orderBy('created_at', 'desc');
}
])->find(1);

答案 4 :(得分:1)

我喜欢更简单的方法,提到here

在您的User模型中,除现有messages()关系外,添加此关系

public function latestMessage() 
{
    return $this->hasOne(Message::class, 'recipient_id')->latest();
}

然后当您查询这样的查询时。

$messages = User::with('latestMessage')->get();

$messages包含每个用户的最新消息。

修改

为了按datetime / id排序结果,你可以这样做。

$messages = User::with(['latestMessage' => function($message) {
    $message->orderBy('id', 'desc');
}])->get();

$messages包含id订购的每位用户的最新消息。 Refer this answer

答案 5 :(得分:0)

这是最有说服力的方式&#34;我发现这样做了:

在用户模型中:

public function messages() {
    return $this->hasMany(Message::class, 'recipient_id');
}

public function latestMessagePerSender()
{
    return $this->messages()
        ->whereNotExists(function ($query) {
            $query->from('messages AS m2')
                ->whereRaw('m2.sender_id = messages.sender_id')
                ->whereRaw('m2.created_at > messages.created_at');
    });
}

然后只是$user->latestMessagePerSender

答案 6 :(得分:0)

也许你可以尝试这个:

        $user = \Auth::user(); 

        // Get the latest date
        $last_date_created = Message::latest()->first()->created_at; // also check for possible null

        // Get the date only - use to specify date range in the where section in the eloquent query
        $target_date = date('Y-m-d', strtotime( $last_date_created ) );

        // Retrieve the messages
        $latest_posts = Message::where('recipient_id', $user->id)
                               ->where('created_at', '>=', $target_date . ' 00:00:00')
                               ->where('created_at', '<=',  $target_date . ' 23:59:59')
                               ->groupBy('sender_id')
                               ->groupBy('created_at')
                               ->get();

        return $latest_posts;

这可能效率不高,因为它需要两个查询来完成这项工作,但您将通过代码可读性获益。

我希望它的运作方式应该如此。虽然我还没有对它进行过测试,但我通常会这样做...干杯!

答案 7 :(得分:0)

我在另一个论坛上找到了此解决方案,我认为这就是您想要的。我发布了它,以便对其他用户有用

$conflictTraining = Training::where('fk_motorbike', $fk_motorbike)  
        ->whereDate('date_seance', "=" , $date_start)  
        ->where('hour_start', "<=" , $request->get('hour_start')) 
        ->where('hour_end', ">=" , $request->get('hour_end'))
        ->where('fk_former', $request->get('fk_former'))
        ->first();  

        $conflictRevision = Revision::where('fk_motorbike', $fk_motorbike)
        ->whereDate('date_revision_start', "<=" , $date_start)
        ->whereDate('date_revision_end', ">=", $date_start)
        ->where('hour_start', "<=" , $request->get('hour_start'))  
        ->where('hour_end', ">=" , $request->get('hour_end'))
        ->first();

        $conflictFormer = Training::where('fk_former', $fk_former)  
        ->whereDate('date_seance', "=" , $date_start)  
        ->where('hour_start', "<=" , $request->get('hour_start')) 
        ->where('hour_end', ">=" , $request->get('hour_end'))
        ->first();  

        $conflictMotorbike = Training::where('fk_motorbike', $fk_motorbike)  
        ->whereDate('date_seance', "=" , $date_start)  
        ->where('hour_start', "<=" , $request->get('hour_start')) 
        ->where('hour_end', ">=" , $request->get('hour_end'))
        ->first();  


        if(isset($conflictTraining)){
            return redirect()->route('trainings.index')
            ->with('error', 'Duplicate ! ');
        }

        if(isset($conflictRevision)){
            return redirect()->route('trainings.index')
            ->with('error', 'The motorbike is in revision! ');
        }

        if(isset($conflictFormer)){
            return redirect()->route('trainings.index')
            ->with('error', 'Duplicate former ! ');
        }

        if(isset($conflictMotorbike)){
            return redirect()->route('trainings.index')
            ->with('error', 'Duplicate motorbik ! ');
        }

原始帖子:https://laracasts.com/discuss/channels/laravel/get-the-latest-message-of-chat-model-with-mysql-just-cannot-get-the-idea-how-to-do-this?page=1#reply=392529

答案 8 :(得分:-1)

你可以尝试这个

Messages::where('recipient_id',**{USER_ID}**)
          ->group_by('sender_id')
          ->order_by('id','desc')
          ->get();