via() 返回值拼错或渠道未注册会导致通知静默失败;数据库通知需添加notifiable_id+type及+read_at联合索引;短信需自定义channel并注册;标记已读应直接where更新而非find后操作。

via() 返回值拼错或渠道未注册,通知就静默消失
通知发不出去,大概率不是 SMTP 配错了,也不是队列没跑,而是 via() 方法返回了一个 Laravel 根本不认识的字符串。它不会报错,也不会打日志,直接跳过整条通知。
常见错误包括:
-
'mails'(多写了个s)→ 应该是'mail' -
'database'用了但没执行php artisan notifications:table && php artisan migrate→ 表不存在,插入失败且无提示 -
'twilio'写了但没在AppServiceProvider中用Notification::extend()注册 Channel 类 -
return ['mail', 'database']却只实现了toMail(),漏了toDatabase()→ 数据库渠道被忽略
检查方法:在 via() 返回前加一行 dd($notifiable);,确认代码确实执行到了这里;再确认 config/services.php 和服务包是否已安装、注册。
数据库通知查得慢?缺联合索引是硬伤
notifications 表一旦用户量上来,$user->unreadNotifications 就开始卡,这不是 PHP 性能问题,是 SQL 查询没走索引。
必须加的索引只有两个:
-
notifiable_id + notifiable_type联合索引 —— 所有按用户查通知的基础 -
notifiable_id + notifiable_type + read_at复合索引 —— 如果你频繁做「未读筛选」或分页
data 字段是 JSON 存的,别指望 whereJsonContains('data', [...]) 能高效查询——它不走索引,数据一多就拖垮整个接口。需要按内容检索?提前把关键字段(比如 topic_id、reply_id)冗余到单独字段并建索引。
短信要自己写 Channel,没有 toSms() 这种通用方法
Laravel 官方不提供统一的短信抽象,因为 Twilio、Vonage、阿里云、腾讯云的 API 差异太大:签名方式、模板规则、参数名、回调路径全都不一样。
所以你得自己封装一个 Channel 类,并显式绑定:
- 命名要跟
via()里一致,比如写return ['twilio'],就必须有toTwilio()方法,不能叫toSms() - Channel 类需实现
send()方法,处理 HTTP 请求、异常捕获、重试逻辑 - 在
AppServiceProvider::boot()中注册:Notification::extend('twilio', function ($app) { return new TwilioChannel($app['twilio.client']); });
漏掉任何一环,via() 里写了也白写。
标记已读别先 find() 再 markAsRead()
常见写法:$notification = $user->notifications()->find($id); $notification->markAsRead(); 看似自然,其实多一次主键查询。
更高效的做法是直接 where + update:
- 单条:
$user->notifications()->where('id', $id)->markAsRead(); - 批量:
$user->notifications()->whereIn('id', $ids)->markAsRead();
如果只是取未读数,$user->unreadNotifications->count() 比 $user->notifications()->whereNull('read_at')->count() 快得多——前者走的是预加载的 Eloquent 关系缓存,后者每次都是全表扫描。











