skip to Main Content

I’m building an API with Laravel and want to send push notification using the Laravel Notifications system. I’ve a model for matches (which is basically a post), another user can like this match. When the match is liked, the creator of the post will get a push notification. It’s just like Instagram, Facebook, etc.

Often the push notification wasn’t send to the user. I installed Laravel Horizon to see if there where errors. Sometimes the notification was send and sometimes it wasn’t. With the exact same data:

Laravel Horizon list

The notification fails sometimes with the exact same data (same user, same match).

The error is as followed:

IlluminateDatabaseEloquentModelNotFoundException: No query results
for model [AppModelsMatch] 118 in
/home/forge/owowgolf.com/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:312

I’m sure the match and the user exists in the database, I’ve verified that before sending the notification. Does anybody know what’s going wrong? Everything I could find online is that people didn’t save their model before sending the notification into the queue. But the line where the code send’s the notification into the queue wouldn’t even be reached if the model didn’t exists. Because of Implicit Binding in the route/controller.

Controller method:

/**
 * Like a match.
 *
 * @param  AppModelsMatch  $match
 * @return IlluminateHttpJsonResponse
 */
public function show(Match $match)
{
    $match->like();

    $players = $match->players()->where('user_id', '!=', currentUser()->id)->get();

    foreach ($players as $user) {
        $user->notify(new NewLikeOnPost($match, currentUser()));
    }

    return ok();
}

Notification:

<?php

namespace AppNotifications;

use AppModelsMatch;
use AppModelsUser;
use IlluminateBusQueueable;
use NotificationChannelsApnApnChannel;
use NotificationChannelsApnApnMessage;
use IlluminateNotificationsNotification;
use IlluminateContractsQueueShouldQueue;

class NewLikeOnPost extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * The match instance.
     *
     * @var AppModelsMatch
     */
    private $match;

    /**
     * The user instance.
     *
     * @var AppModelsUser
     */
    private $user;

    /**
     * Create a new notification instance.
     *
     * @param  AppModelsMatch  $match
     * @param  AppModelsUser  $user
     */
    public function __construct(Match $match, User $user)
    {
        $this->user = $user;
        $this->match = $match;

        $this->onQueue('high');
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  AppModelsUser  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        if ($notifiable->wantsPushNotification($this)) {
            return ['database', ApnChannel::class];
        }

        return ['database'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  AppModelsUser  $notifiable
     * @return NotificationChannelsApnApnMessage
     */
    public function toApn($notifiable)
    {
        return ApnMessage::create()
            ->badge($notifiable->unreadNotifications()->count())
            ->sound('success')
            ->body($this->user->username . ' flagged your match.');
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            'user_id' => $this->user->id,
            'body' => "<flag>Flagged</flag> your match.",
            'link' => route('matches.show', $this->match),
            'match_id' => $this->match->id,
        ];
    }

    /**
     * Get the match attribute.
     *
     * @return AppModelsMatch
     */
    public function getMatch()
    {
        return $this->match;
    }
}

3

Answers


  1. Check your .env to be sure that u really use REDIS

    BROADCAST_DRIVER=redis
    CACHE_DRIVER=redis
    SESSION_DRIVER=redis
    SESSION_LIFETIME=120
    QUEUE_DRIVER=redis
    

    then clear cache ( php artisan cache:clear , php artisan view:clear ), that should clear the issue

    EDIT

    I had similar problems but now I use Docker only and before I had to check for cached configfiles, wrong file/folderpermissions and so on (REDIS for broadcast only, others were standard). I started using redis only – that`s a lot easier, faster and more debugfriendly for me ! And together with Docker really helpful to not use messed up nginx/apache/php/redis/ …

    Login or Signup to reply.
  2. Its probably because $user is not an object of User model, its an object of Match model. You need to do a User::findorfail or User::firstOrFail then notify the user.

    public function show(Match $match)
    {
    $match->like();
    
    $players = $match->players()->where('user_id', '!=', currentUser()->id)->get();
    
    foreach ($players as $user) {
        $someUser = User::findOrFail($user->user_id);
        $someUser->notify(new NewLikeOnPost($match, currentUser()));
    }
    
    return ok();
    

    }

    Unless the notify trait is used in Match model. Or you could use eager loading which will cost way less queries!

    Login or Signup to reply.
  3. This is not a complete solution, but it will lower your chances of running into this error in the future.

    Instead of passing in the whole Match model into the job, only pass the id of the model. You can then fetch that model in the constructor.

    /**
     * Like a match.
     *
     * @param  AppModelsMatch  $match
     * @return IlluminateHttpJsonResponse
     */
    public function show(Match $match)
    {
        $match->like();
    
        $players = $match->players()->where('user_id', '!=', currentUser()->id)->get();
    
        foreach ($players as $user) {
            $user->notify(new NewLikeOnPost($match->id, currentUser()->id));
        }
    
        return ok();
    }
    

    Notification:

    class NewLikeOnPost extends Notification implements ShouldQueue
    {
        use Queueable;
    
        private const QUEUE_NAME = 'high';
    
        /**
         * The match instance.
         *
         * @var AppModelsMatch
         */
        private $match;
    
        /**
         * The user instance.
         *
         * @var AppModelsUser
         */
        private $user;
    
        /**
         * Create a new notification instance.
         *
         * @param  int $match
         * @param  int $user
         */
        public function __construct(int $matchId, int $userId)
        {
            $this->user = User::query()->where('id', $userId)->firstOrFail();
            $this->match = Match::query()->where('id', $matchId)->firstOrFail();
    
            $this->onQueue(self::QUEUE_NAME);
        }
    
        // Rest of the class is still the same...
    }
    

    You can use the SerializesModels trait, but it doesn’t work well when you add a delay to a queued job. This is because it will try to reload the model on __wakeup() and sometimes it cannot find the class.

    Hopefully this helps 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search