LoginSignup
1
1

More than 1 year has passed since last update.

[Laravel] 複数の通知タイプの合計未読数を取得する

Posted at

Laravel のデータベース通知を使って未読数を取得します。

通知タイプが1つの場合

通常,通知を取得する場合は1つの通知タイプ(notificationsテーブルtypeカラム)のみを扱うことが多いです。

例えば,未読のメッセージ数は以下のようにして取得できます。

$user->unreadNotifications()->where('type', 'App\Notifications\MessageReceived')->count();

通知タイプが複数の場合

チャットアプリを例に挙げます。

「メッセージを受信した」「グループに招待された」のように,複数の通知タイプの合計数を表示したい場面があるかもしれません。

以下のように書きたくなりますが,これでは想定通りに動きません。

$user()->unreadNotifications()->where('type', 'App\Notifications\MessageReceived')->orWhere('type', 'App\Notifications\GroupInvited')->count();

実行された SQL を見ると,type の条件がグループ化されていないのがわかります。
これだと type = "App\Notifications\GroupInvited" に該当する全てのレコードも含めてしまいます。

select count(*) as aggregate 
from `notifications` 
where `notifications`.`notifiable_id` = 1
and `notifications`.`notifiable_id` is not null 
and `notifications`.`notifiable_type` = "App\Models\User"
and `read_at` is null 
and `type` = "App\Notifications\MessageReceived"
or `type` = "App\Notifications\GroupInvited"
/* 以下の2行は等しい */
WHERE a AND b AND c OR d
WHERE (a AND b AND c) OR d

条件をグループ化して,以下のような SQL を組み立てる必要があります。

WHERE a AND b AND (c OR d)

正しい書き方

クロージャを使ってクエリをグループ化します。

$user()->unreadNotifications()->where(function(Builder $query) {
    $query->where('type', 'App\Notifications\MessageReceived')
          ->orWhere('type', 'App\Notifications\GroupInvited');
})->count();

実行された SQL

select count(*) as aggregate 
from `notifications` 
where `notifications`.`notifiable_id` = 1
and `notifications`.`notifiable_id` is not null 
and `notifications`.`notifiable_type` = "App\Models\User"
and `read_at` is null 
and (`type` = "App\Notifications\MessageReceived" or `type` = "App\Notifications\GroupInvited") 

メソッドとして定義する

毎回クエリを書くのは面倒なのでモデルに定義します。

User.php
use Illuminate\Database\Eloquent\Builder;

public function getChatUnreadCount(): int
{
    return $this
        ->unreadNotifications()->where(function(Builder $query) {
            $query->where('type', 'App\Notifications\MessageReceived')
                  ->orWhere('type', 'App\Notifications\GroupInvited');
        })->count();
}
// チャットの未読通知数を取得
$user->getChatUnreadCount();

任意の通知タイプを扱う

上で定義したメソッドでは条件が固定でした。
汎用的に使えるように,任意の通知タイプを指定できるようにします。

User.php
use Illuminate\Database\Eloquent\Builder;

/**
 * 
 * @param  string[] $types
 * @return int
 */
public function getCombinedUnreadCount(string ...$types): int
{
    if(empty($types)){
        return $this->unreadNotifications->count();
    }
    return $this
        ->unreadNotifications()->where(function(Builder $query) use ($types) {
            $i = 0;
            $name = '';
            foreach($types as $type){
                $name = 'App\\Notifications\\' . $type;
                if($i === 0){
                    $query->where('type', $name);
                }else{
                    $query->orWhere('type', $name);
                }
                ++$i;
            }
        })->count();
}

通知クラス名を文字列で , 区切りにして渡します。

$user()->getCombinedUnreadCount('MessageReceived', 'GroupInvited', 'FriendAdded');
select count(*) as aggregate 
from `notifications` 
where `notifications`.`notifiable_id` = 1
and `notifications`.`notifiable_id` is not null 
and `notifications`.`notifiable_type` = "App\Models\User" 
and `read_at` is null 
and (`type` = "App\Notifications\MessageReceived" or `type` = "App\Notifications\GroupInvited" or `type` = "App\Notifications\FriendAdded") 

最後に

実際は、わざわざメソッドとして定義する必要もないかもしれませんが、何か参考になればと思い書きました。

間違っている部分などがあれば、ご指摘いただけると嬉しいです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1