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

klopma's avatar

Async jobs and user-specific scopes

Sometimes I want to generate reports for users based on the information they have access to, and I keep getting tripped up by how to navigate the async (userless) nature of jobs.

I use global scopes to determine in-app what a user can see, so for example:

  • You are paired with two (school) districts, therefore you can see all schools, classrooms, and students in each of those districts.
  • In the DB I store the user <-> district pairings.
  • To avoid bombarding the DB, when you logs in, I cache the schools and classrooms you should have access to (with a key that's specific to you).
  • My global scopes use this data via a helper a la:
public function apply(Builder $builder, Model $model)
{
    $builder->whereIn('schools.id',  user_school_ids());
}

This allows me to call School::get() to get all of the schools relevant for you.

However, if I want to generate a report on, say, all of the schools that you have access to, I fire off a job and then there is no auth user associated with it. So to get around this, I've had to either:

(a) Pass along the school_ids to the job and duplicate all of my global scopes again as local scopes that can receive a list of school ids or

(b) Pass along the user model and use: Auth::setUser($this->user) to sort of pretend that user is logged in during the job.

Both feel gross. Is there a standard way of doing this?

0 likes
3 replies
itsfg's avatar

I don't know if there's a standard way to do this. I'm happy to get the answer if someone knows it.

I personnaly use your option (b), the job logs in the user using Auth::onceUsingId($user_id), $user_id is given as a paramater of the job.

I've also added a Auth::logout() at the end the job, because I got some strange cases where one job for User A was processed with User B. I guess Job queues are kind of static / permanent threads and the authentication operation is common to all jobs. (happy to get confirmations about this matter too).

But your option (a) doesn't seem gross to me neither. I guess using (a) or (b) could depend on the complexity of the parameters to be communicated to your job.

A long answer for basically saying "I don't know" =)

klopma's avatar

Hey, thanks for the reply! It's nice to hear back that this is an issue others have encountered before, and to have a bit of confirmation on why option (b) might be a problem and how to get around it. I might stick with (b) instead of (a) for the most part just so I don't have to rewrite scopes as often.

klopma's avatar

Ran into a humorous scenario recently where I started trying to login at the start of jobs and use Auth::logout() at the end. My test suite typically runs with the sync driver for jobs, and Auth::logout() actually logs me out during execution of those tests. So if I login in the test and fire off the job (which logs out at the end of it), I have to log back in in the middle of the test. Ended up adding a conditional in the job a la

if(!app()->runningUnitTests()) {
    Auth::logout();
}

That's working for the time being, but feels somewhat less than clean.

Please or to participate in this conversation.