Skip to content

Comments

[12.x] Preserve notification state mutations from via() in sendNow()#58558

Merged
taylorotwell merged 2 commits intolaravel:12.xfrom
alimorgaan:@fix/set-notification-locale-after-via
Feb 3, 2026
Merged

[12.x] Preserve notification state mutations from via() in sendNow()#58558
taylorotwell merged 2 commits intolaravel:12.xfrom
alimorgaan:@fix/set-notification-locale-after-via

Conversation

@alimorgaan
Copy link
Contributor

@alimorgaan alimorgaan commented Jan 30, 2026

Summary

Fixes bug where locale set dynamically in via() is lost when sending notifications via sendNow().

Problem

Inconsistency between queueNotification() and sendNow()

Both methods clone notification to $original for sending, but call via() on different objects:

queueNotification() ✅ Correct

protected function queueNotification($notifiables, $notification)
{ 
    $notifiables = $this->formatNotifiables($notifiables);
    $original = clone $notification;

    foreach ($notifiables as $notifiable) {
        foreach ($original->via($notifiable) as $channel) {
        //       ^^^^^^^^^ uses $original

sendNow() ❌ Bug

public function sendNow($notifiables, $notification, array $channels = null)
{
    $original = clone $notification;

    foreach ($notifiables as $notifiable) {
        if (empty($viaChannels = $channels ?: $notification->via($notifiable))) {
        //                                    ^^^^^^^^^^^^^ uses $notification (pre-clone)

        $this->withLocale($this->preferredLocale($notifiable, $notification), ...);
        //                                                    ^^^^^^^^^^^^^ also wrong

When via() mutates state (e.g., sets $this->locale), those changes happen on $notification but $original is sent — losing the mutations.

Real-world use case

Setting notification locale dynamically based on notifiable:

public function via($notifiable)
{
    $this->locale = $notifiable->preferred_language;

    return ['mail', 'database'];
}

Works for queued notifications, fails silently for synchronous ones.

Solution

Align sendNow() with queueNotification():

- if (empty($viaChannels = $channels ?: $notification->via($notifiable))) {
+ if (empty($viaChannels = $channels ?: $original->via($notifiable))) {

- $this->withLocale($this->preferredLocale($notifiable, $notification), ...);
+ $this->withLocale($this->preferredLocale($notifiable, $original), ...);

Test Plan

  • Added testItPreservesLocaleSetInViaMethod to verify locale set in via() is preserved
  • All existing notification tests pass

@rodrigopedra
Copy link
Contributor

+1 on aligning the behavior between sendNow() with queueNotification()

My gripe is with the example choice when the documented way to set a notification's locale based on the notifiable is to make the notifiable implement the Illuminate\Contracts\Translation\HasLocalePreference trait.

Reference: https://laravel.com/docs/12.x/notifications#user-preferred-locales

I am not sure if a notification should be mutated on the via* or with* methods.

I guess any notifiable specific change should be done on the to* methods (toDatabase(), toMail(), etc.).

Anyway, I think it is still worth aligning the behavior between the methods.

@taylorotwell
Copy link
Member

Yeah this way of setting the locale is weird and we don't need to test explicitly for that in my opinion. I would at least change the test to be something realistic.

@taylorotwell taylorotwell marked this pull request as draft February 1, 2026 02:34
@alimorgaan alimorgaan changed the title [12.x] Fix locale set in via() being lost in sendNow() [12.x] Preserve notification state mutations from via() in sendNow() Feb 1, 2026
@alimorgaan
Copy link
Contributor Author

+1 on aligning the behavior between sendNow() with queueNotification()

My gripe is with the example choice when the documented way to set a notification's locale based on the notifiable is to make the notifiable implement the Illuminate\Contracts\Translation\HasLocalePreference trait.

Reference: https://laravel.com/docs/12.x/notifications#user-preferred-locales

I am not sure if a notification should be mutated on the via* or with* methods.

I guess any notifiable specific change should be done on the to* methods (toDatabase(), toMail(), etc.).

Anyway, I think it is still worth aligning the behavior between the methods.

@rodrigopedra
Thanks for the feedback! You're right - using locale as the example was a poor choice since HasLocalePreference is the proper way to handle that. so it's more about preserving the state of the notification object and aligning the behavior

@alimorgaan
Copy link
Contributor Author

Yeah this way of setting the locale is weird and we don't need to test explicitly for that in my opinion. I would at least change the test to be something realistic.

@taylorotwell

Thanks for the feedback! Agreed - locale was a bad example since HasLocalePreference is the proper way. Updated test to verify generic state mutations in via() are preserved. Renamed PR accordingly.

Call via() on $original instead of $notification so locale changes made
in via() are preserved when sending notifications synchronously.
- testItPreservesLocaleSetInViaMethod -> testItPreservesNotificationStateMutatedInViaMethod
- DummyNotificationWithLocaleInVia -> DummyNotificationWithViaMutation
- Remove locale mocking, test generic channelData mutation instead
@alimorgaan alimorgaan force-pushed the @fix/set-notification-locale-after-via branch from 210ee80 to 399aa7e Compare February 1, 2026 09:44
@alimorgaan alimorgaan marked this pull request as ready for review February 1, 2026 09:48
@taylorotwell taylorotwell merged commit bba3886 into laravel:12.x Feb 3, 2026
70 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants