Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No query results for model [App\Models\Match]

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:

Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Models\Match] 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  \App\Models\Match  $match
 * @return \Illuminate\Http\JsonResponse
 */
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 App\Notifications;

use App\Models\Match;
use App\Models\User;
use Illuminate\Bus\Queueable;
use NotificationChannels\Apn\ApnChannel;
use NotificationChannels\Apn\ApnMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;

class NewLikeOnPost extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * The match instance.
     *
     * @var \App\Models\Match
     */
    private $match;

    /**
     * The user instance.
     *
     * @var \App\Models\User
     */
    private $user;

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

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

    /**
     * Get the notification's delivery channels.
     *
     * @param  \App\Models\User  $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  \App\Models\User  $notifiable
     * @return \NotificationChannels\Apn\ApnMessage
     */
    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 \App\Models\Match
     */
    public function getMatch()
    {
        return $this->match;
    }
}
like image 474
Dees Oomens Avatar asked Oct 05 '17 08:10

Dees Oomens


1 Answers

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  \App\Models\Match  $match
 * @return \Illuminate\Http\JsonResponse
 */
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 \App\Models\Match
     */
    private $match;

    /**
     * The user instance.
     *
     * @var \App\Models\User
     */
    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 :)

like image 167
Simon D. Avatar answered Oct 21 '22 22:10

Simon D.