ChatableScheduler ​
Unusualify\Modularous\Schedulers\ChatableScheduler
Artisan command that polls every minute for unread chat messages and dispatches ChatableUnreadNotification to the relevant parties. It is the heartbeat of the in-app chat notification system.
Signature ​
modularous:scheduler:chatableNo options or arguments.
What It Does ​
On each run the command:
Discovers all
Chatablemodels viaModularousFinder::getModelsWithTrait(Chatable::class)— this returns every Eloquent model in the application that uses theChatabletrait.For each model class, queries instances that have an actionable unread message using the
hasNotifiableMessagescope (in chunks of 100).For each matching instance, calls
$item->handleChatableNotification()which decides whether to fire a notification and to whom.
The hasNotifiableMessage Scope ​
Unusualify\Modularous\Entities\Scopes\ChatableScopes::scopeHasNotifiableMessage
Finds model instances where the latest chat message:
- is not read (
is_read = false) - has not yet been notified (
notified_at IS NULL) - was created at least
$minuteOffsetminutes ago (when provided)
-- Simplified view of what the scope produces:
SELECT chatable.*
FROM chatable
WHERE EXISTS (
SELECT 1 FROM chats
WHERE chats.chatable_id = chatable.id
AND EXISTS (
SELECT 1 FROM chat_messages
WHERE chat_messages.chat_id = chats.id
AND chat_messages.is_read = 0
AND chat_messages.notified_at IS NULL
AND chat_messages.created_at = (
SELECT MAX(m2.created_at) FROM chat_messages m2
WHERE m2.chat_id = chat_messages.chat_id
AND m2.deleted_at IS NULL
)
)
)Notification Logic (handleChatableNotification) ​
Chatable::handleChatableNotification() decides who gets notified:
latestChatMessage exists?
AND is_read = false?
AND created_at older than $chatableNotificationInterval minutes?
AND notified_at IS NULL?
│
├── dispatch UnreadChatMessage event
│
├── resolve chatableCreator (via HasCreator)
├── resolve chatableAuthorizedUser (via HasAuthorizable, if is_authorized)
│
└── messageCreator exists?
│
├── chatableCreator != messageCreator → notify chatableCreator
└── chatableAuthorizedUser != messageCreator → notify chatableAuthorizedUserThe notification sent is ChatableUnreadNotification. See System Notifications.
Notification Interval ​
The guard created_at older than $chatableNotificationInterval minutes prevents spamming notifications for very recent messages. The interval defaults to 60 minutes and can be overridden per model:
class Order extends Model
{
use Chatable;
// Notify only if the message is older than 30 minutes
protected static int $chatableNotificationInterval = 30;
}Schedule ​
Registered as ->everyMinute() in BaseServiceProvider:
$schedule->command('modularous:scheduler:chatable')->everyMinute();Running every minute ensures the notification is sent as close to the interval boundary as possible without requiring a separate queue worker for polling.
Manual Usage ​
php artisan modularous:scheduler:chatableUseful for testing the notification pipeline or recovering from a scheduler outage.
Error Handling ​
The entire handle() body is wrapped in a try/catch:
try {
// discovery + chunked processing
} catch (\Throwable $th) {
Log::channel('scheduler')->error('Modularous: Chatable scheduler error', [
'error' => $th->getMessage(),
'trace' => $th->getTraceAsString(),
]);
}A failure in one model's processing does not surface to the scheduler — the error is logged and the run completes silently. Check storage/logs/scheduler.log if notifications stop firing.
Related ​
Chatableentity trait — adds chat relationships andhandleChatableNotificationto a model.- ChatableUnreadNotification — the notification class dispatched per unread chat.
ChatableScopes— the query scopes used to find notifiable models.