duddy67's avatar

Custom variable removed in the handle method

Hi,

Can someone explain what's happening here ?

class SendTestEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $data;

    /** 
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(object $data)
    {   
        $this->data = $data;
        logger('__construct()'.$this->data->subject);
    }   

    /** 
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {   
        logger('handle(): '.$this->data->subject);
         Mail::to($data->email)->send(new AppMailer($data));
    }
}  

Email model:

    public static function sendTestEmail()
    {   
        $data = auth()->user();
        $data->subject = 'Test email';
        SendTestEmail::dispatch($data);
    }

Result:

[2024-02-03 14:29:21] local.DEBUG: __construct(): Test email
[2024-02-03 14:29:22] local.DEBUG: handle():

It looks like the subject variable is removed between the constructor and the handle method. Where does this behaviour come from and how can I prevent it ? Thanks

0 likes
3 replies
Snapey's avatar
Snapey
Best Answer
Level 122

Its because when the $data is queued, laravel recognises that $data is actually a User model and so only queues the model ID. Later when the job is run, the model is fetched from the database, but subject is not a model property so it does not get restored.

One solution is to just pass them separately.

use App\Models\User;

class SendTestEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected User $user;
    protected string $subject;

    /** 
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user, string $subject)
    {   
        $this->user = $user;
		$this->subject = $subject;

        logger('__construct()'.$this->subject);
    }   

    /** 
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {   
        logger('handle(): '.$this->subject);
         Mail::to($user->email)->send(new AppMailer($user));
    }
}  

Email model:

    public static function sendTestEmail()
    {   
        SendTestEmail::dispatch(auth()->user(), 'Test email');
    }

ps, in PHP 8.1, you can simplify

    /** 
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct (
            protected User $user,
            protected string $subject
    ) {   
        logger('__construct()'.$this->subject);
    }   
1 like
JussiMannisto's avatar

When you dispatch a job, its payload data is serialized and stored while it waits to be handled. When your job has the SerializesModels trait, models are serialized in a special way. They aren't stored as a snapshot of their current state, instead only their identifiers are stored. An up-to-date version of the model is then pulled from the DB when when the job is handled. So if you set some dynamic properties on the model, or make any changes and don't save the model, those changes won't be present when the job is handled.

The solution is simple: don't use the user model to pass the subject value to the job. It doesn't belong there anyway. Use a separate variable for that. Also I think it's a bit weird that the constructor uses an object type hint, but that's neither here nor there.

1 like

Please or to participate in this conversation.