Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

rdelorier's avatar

What is best practice for identifying the user with queued jobs

I have a number of places where I rely on Illuminate\Auth\Guard to give the the current user. I haven't had any issues until about an hour ago when I decided it was time to start using a queue to speed the site up some.

Problem: When a user updates a job task I need examine all of that jobs tasks to update the jobs overall status accordingly, I've already written the task event handler and everything works well. When I queue the same event I run into errors like my RecordsActivity trait not knowing which user just updated the job status, or my TaskRequirementsProvider not knowing which account settings should be used.

I Came up with two solutions one being much easier than the second.

Solution 1: I can go around to everywhere that expects a user to be logged in and find another way to make the user accessible. EX: set the account id on the provider before asking for required tasks

This could take quite a while, Most of the software needs to know who is logged in and I always just ask guard for the user, This is extremely convenient and this is the first issue I have had with doing it this way.

Solution 2. I can just include the user in the event and set them as the current user in the handler. This definitely handles the current issues but are there any caveats to this? I've never really set the user except to allow super users to log into another persons account. I pretty much used the default authentication scaffolding and haven't had to deal much with managing the guard state so a am a bit skeptical how this method may be viewed by other developers.

If anyone has time ( after reading this book >.< ) and would care to share some insight into the pros and cons of either solution it would be much appreciated.

0 likes
17 replies
BENderIsGr8te's avatar

I'm going to have to move some of my background tasks to Queues and I was looking into a solution like that for when I have to implement it. What I decided to try is to pass the User ID to the queue, and then have the queue handler Set the authorized user by the ID using the following

$userId; // Passed when I queued the job
Auth::loginUsingId($userId);

I am hoping this will essentially make all my Auth::user() requests work fine without having to refactor code. Maybe give that a shot and see how it works.

rdelorier's avatar

@BENderIsGr8te I should have included a code sample but thats actually what solution 2 is. Are there any pitfalls with this method? It's certainly much easier that the alternative.

BENderIsGr8te's avatar
Level 6

@rdelorier it's apparent that I can't read because I must have fallen asleep before I finished reading your book ;)

I wouldn't think there would be any pitfalls as long as you don't get numerous errors when testing. The User is just set in a session variable. Assuming that Jobs/Commands/Queues still have access to session variables then it should work fine as long as you aren't trying to get the users URL or IP address or anything funky like that.

Maybe someone that has more experience with Queues that has dealt with this issue has a better solution, but I would be surprised if your Solution #2 wasn't the exact solution because I really can't think of that many use cases for Auth::loginUsingId().

olimorris's avatar

Why wouldn't you just pass the $userId as a variable to the queue? Why are they protected by Auth middleware? Perhaps I'm missing something...

BENderIsGr8te's avatar

There is no middleware here. The Auth::user() method is being used throughout his project to get the logged in user to perform various tasks. As he is moving some of these time consuming tasks to queued jobs, he is trying to avoid re-writing all his methods. He is trying to find out if using Auth::user() to get the current user (while all the time, mocking the current user) is going to cause him issues throughout his project.

pmall's avatar

A queued job has no notion of session or login. This is a non-sense.

If you need an user in a queued job, just pass it the user or the user id.

1 like
cbojer's avatar

I will have to agree with @pmall here. The queue shouldn't be using sessions, rather it should just be handed the user id so it can update what needs to be updated. If it requires a restructuring of a bit of code, I would say it's for the better in the long run, since this is probably an indication of your code being tighly coupled with Laravel's Guard.

1 like
rdelorier's avatar

@pmall It's not necessarily the queued job that needs the user. Say the user changes the tasks required, I need to examine all open jobs (no limit to how many there may be) and add/remove required tasks. Removing/Adding a task to/from a job could cause the job status to change which fires and event that creates an activity "{Auth::user()->name} has completed {$job->id}", Are you saying its inappropriate for my model events to request the current user from Auth and I should instead find another way to tell the model which user is updating it?

@cbojer You are certainly correct about by code being highly dependent on Laravels Guard but I cannot see myself ever trying to move to another framework and assumed it was acceptable. Should Guard not be used outside of http requests?

@BENderIsGr8te I agree that there aren't many other use cases for loginUsingId and using this method was quick and easy, I updated all jobs to be queued and tested with this solution without and further issues. I think I'll stick with this until I have an issue pop up.

Thank you all for taking the time to read and reply.

pmall's avatar

"{Auth::user()->name} has completed {$job->id}"

Again, it is a non sense to speak about current logged in user for a job. It is not run in the context of a request. The whole purpose of a job is to be run out of context.

You must send to the job all the data it needs. So when a user starts a job, just send it his id with the rest of the data. Then "{User::find($user_id)->name} has completed {$job->id}".

I have no idea why you want to use Auth::loginUsingId($user_id) instead of User::find($user_id). You are using the Auth functionality to simulate the selection of an user.

1 like
victorcesar's avatar

@pmall For me the real motive to use Auth::loginUsingId($user_id); is that i have a Observer Logger in the table that the job is updating so i can register the logs of the changes and i get the user in the observer by Auth::user()

So doing this allows me to save the authenticated user that execute the job like an import job for example. in me Observer

rdelorier's avatar

@pmall I think you're misunderstanding, I'm not using auth to get the user in the job, I'm using auth to get the user in other parts of the app that are triggered by the queued job and have absolutely no idea that the job even exists. You say its non-sense to use auth to mock the logged in user where I feel like its non-sense to ignore an integral part of the framework simply because I queued the job instead of completing it within the users request.

pmall's avatar

I'm using auth to get the user in other parts of the app that are triggered by the queued job and have absolutely no idea that the job even exists.

I'm curious to see this job's code :)

rdelorier's avatar
class JobSettingsHandler implements  ShouldBeQueued{

    /** @var ValidatorProvider */
    private $provider;

    /** @var Guard  */
    private $auth;

    private $requiredValidators = [];

    /**
     * Create the event handler.
     *
     * @param ValidatorProvider $provider
     */
    public function __construct(ValidatorProvider $provider, Guard $auth)
    {
        $this->provider = $provider;
        $this->auth = $auth;
    }

    /**
     * Handle the event.
     *
     * @param  AccountSettingsUpdated  $event
     * @return void
     */
    public function handle(AccountSettingsUpdated $event)
    {
        $this->auth->loginUsingId($event->userId);
        $this->removeOrphandedValidators($event);
        $this->requiredValidators = $this->provider->validationRequired($event->jobTypes);

        $query = Job::whereAccountId($event->accountId)
            ->whereIn('job_type', $event->jobTypes)
            ->open();

        //bite size chunks
        $query->chunk(1000, function(Collection $jobs){
//          dd($jobs);
            foreach($jobs as $job){
                $job->updateStatus(true);
            }
        });
    }


    /**
     * Find an remove all job validations that are no longer needed
     * @param AccountSettingsUpdated $event
     */
    private function removeOrphandedValidators(AccountSettingsUpdated $event)
    {
        foreach($this->provider->validationRequired() as $jobType => $requirements){
            //delete the validators where job type = $jobType and validation not in required
            $query = JobValidation::whereHas('job', function($query) use ($event, $jobType, $requirements)
            {
                //Filter by account
                $query->byAccountId($event->accountId);

                //Only open jobs
                $query->open();

                /*
                 * include only validators where the job type does
                 * not require the validator type
                 */
                $i = 0;

                $query->where(function($query) use ($jobType, $requirements) {
                    $query->where('jobs.job_type', '=', $jobType)
                        ->whereNotIn('job_validations.validatable_type', $this->getQualifiedNames($requirements));
                });
            });

            //We need to call delete on the model so it can do its own cleanup
            //otherwise we could replace chunk with delete
            $query->chunk(100, function(Collection $validators){
                $validators->each(function($v){ $v->delete(); });
            });
        }
    }

    /**
     * Transforms array of validation types into fully qualified class names
     * @param array $requiredValidators
     * @return array
     */
    private function getQualifiedNames(array $requiredValidators)
    {
        return array_map(function($typeName){
            return $this->provider->getModelClassName($typeName);
        }, $requiredValidators);
    }

}

when i call job->updateStatus a lot of things happen and many events could be triggered, some of which need to know who is logged in.

pmall's avatar

Ok. My two cents is you should try to abstract the user when using events/jobs by passing the user to them when firing them.

$job->updateStatus($user, true);

It is just an advice you do whatever you want :)

2 likes
BENderIsGr8te's avatar

It's funny how many different approaches there are to the same problem. None of them are wrong, some are just better practices than the other.

I think the use of Auth:loginUsingId() is a band-aid until he has time to re-factor dozens and dozens of functions to accept the (array'ed version) of a User. I only have a few commands/jobs at this point with a ton to write, then I am going to move some of them to queued. So I will probably abstract them out from the beginning knowing all of this :)

klopma's avatar

@BENderIsGr8te @rdelorier I know this question's coming wildly after the fact, but how did you folks end up handling your respective situations?

I'm in a similar situation, where I designed all of my global scopes to make decisions based on the auth()->user() at the time (e.g. if you're one auth()->user(), you should only be able to access models in this way). So when I run async jobs, I don't have that info and the global scopes just sort of fall apart. I've asked about this elsewhere and the conclusion was basically:

Create some sort of "Context" class that gets populated during construction of a job or during a normal request cycle (e.g. during middleware?) with the Auth()->user() while it still exists. Then update the global scopes to reference that Context::getUser() instead.

Does that make sense to you folks? And if I'm calling it during the job, I guess I need to save the user during the constructor and actually set the Context during the handle method?

Please or to participate in this conversation.