trevorpan's avatar

What triggers `Serialization of 'Closure' is not allowed` when using Queue?

This is from abstract class Queue

    /**
     * Create a payload for an object-based queue handler.
     *
     * @param  object  $job
     * @param  string  $queue
     * @return array
     */
    protected function createObjectPayload($job, $queue)
    {
        $payload = $this->withCreatePayloadHooks($queue, [
            'displayName' => $this->getDisplayName($job),
            'job' => 'Illuminate\Queue\CallQueuedHandler@call',
            'maxTries' => $job->tries ?? null,
            'delay' => $this->getJobRetryDelay($job),
            'timeout' => $job->timeout ?? null,
            'timeoutAt' => $this->getJobExpiration($job),
            'data' => [
                'commandName' => $job,
                'command' => $job,
            ],
        ]);

        return array_merge($payload, [
            'data' => [
                'commandName' => get_class($job),
                'command' => serialize(clone $job), // this line is where the exception is thrown
            ],
        ]);
    }

In my particular case I have an InvitationsRegisterController that has an invitation code.

    /**
     * Get a validator for an incoming registration request.
     *
     * @param array $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        $invitation = Invitation::findByInvitationCode(request('invitation_code'));

        abort_if($invitation->hasBeenUsed(), 404);

        return Validator::make($data, [
            'firstname' => 'required|string|max:255',
            'lastname' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:12|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param array $data
     * @return User
     */
    protected function create(array $data)
    {
        $invitation = Invitation::findByInvitationCode(request('invitation_code'));

        $user = User::create([
            'firstname' => $data['firstname'],
            'lastname' => $data['lastname'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $invitation->update([
            'user_id' => $user->id,
        ]);

        return redirect('/email/verify');
    }
0 likes
7 replies
Nakov's avatar

Can you please show your job class?

trevorpan's avatar

Hi good morning @nakov

Well, interestingly I noticed the listener did not have a queue setup on it by default - I added that. Could not find a job in the vendor directory as it does not appear to ship with a laravel implementation.

Is the registered email something that should not be queued? The out of the box registered event has a use Illuminate\Queue\SerializesModels; statement.

I'm in the weeds here with familiarity.

<?php

namespace Illuminate\Auth\Listeners;

use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendEmailVerificationNotification implements ShouldQueue
{
    /**
     * Handle the event.
     *
     * @param  \Illuminate\Auth\Events\Registered  $event
     * @return void
     */
    public function handle(Registered $event)
    {
        if ($event->user instanceof MustVerifyEmail && ! $event->user->hasVerifiedEmail()) {
            $event->user->sendEmailVerificationNotification();
        }
    }
}
trevorpan's avatar

@nakov So what you asked hit me this afternoon.

I need to implement a job.

I've started a class InvitedUserRegisteredEmail implements ShouldQueue job but I'm wondering how would that work?

The Registered event fires: SendEmailVerificationNotification::class,. Not sure where or if something like SendEmailVerificationNotification::dispatch(); is a reasonable thing to do as the listener is already picking up on the event.

Have you created anything like this before that might cause the error Serialization of 'Closure' is not allowed?

Nakov's avatar

@trevorpan if the code that you've shared above is the one that is hitting the error I really don't know how. Because you don't send any job from there. I thought you had your own job created before that was causing the error. So now you either have something Custom in the User model that throws the error, or check what you use for a Queue driver then, make sure you have it setup as it should be.

I didn't had any problem with sending email verifications before.

trevorpan's avatar

ok @nakov , thank you for sharing some ideas.

well this is what I've got for phpunit.xml <env name="QUEUE_CONNECTION" value="sync"/>

and in the config/queue.php

 'default' => env('QUEUE_CONNECTION', 'redis'),
...
 'connections' => [
        'redis' => [
            'driver' => 'redis',
            'connection' => 'default',
            'queue' => 'default',
            'retry_after' => 90,
            'block_for' => 5,
        ],

Pretty standard ... not sure.

// user.php
class User extends AuthUser implements Authenticatable, MustVerifyEmail
{
    use  Notifiable, HasRoles;
...

The only other thing that I'm thinking could be the routes.web: Route::middleware(['verified'])->group(function () {...

Could the route group cause this?

trevorpan's avatar
trevorpan
OP
Best Answer
Level 15

@nakov really appreciate your taking a stab at this. It helped me think about other possibilities. In the end, at least it appears, the InvitationsController was using the out of the box Laravel register method. I mistakenly believed I could reuse it.

From what I can see the route cannot be used twice.

The error message is not sufficiently descriptive - possibly worth doing a PR on github and try to improve the message.

So, in the InvitationsController I omitted the protected function validator(array $data) and condensed it to:

/**
     * Create a new user instance after a valid registration.
     *
     * @param Request $data //changed from array $data
     * @return User
     */
    protected function create(Request $data) //changed from array $data
    {
        $invitation = Invitation::findByInvitationCode(request('invitation_code'));
        abort_if($invitation->hasBeenUsed(), 404);

        request()->validate([
            'firstname' => 'required|string|max:255',
            'lastname' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:12|confirmed',
        ]);

        $user = User::create([
            'firstname' => $data['firstname'],
            'lastname' => $data['lastname'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        event(new Registered($user)); // a key addition

        $invitation->update([
            'user_id' => $user->id,
        ]);

        return view('auth.verify');
    }

Please or to participate in this conversation.