Framework: Auth::user() not working inside apply function in global scope

Created on 13 Sep 2018  路  11Comments  路  Source: laravel/framework

  • Laravel Version: 5.5.42
  • PHP Version: 7.1.16
  • Database Driver & Version: PostgreSQL 10.3

Description:

I was trying to create global scope dependent on a role of logged in user (typically, user with some acces rights based on role can see only users with lower access rights).

I user Auth::user() inside apply function of global scope and result was that my nginx server return http error 500. Not laravel error, but error directly from nginx after log time of loading...

However, it started to work, when I added Auth::user() as parameter to addGlobalScope :

 static::addGlobalScope(new UserVisibilityScope(Auth::user())); 

Steps To Reproduce:

Create global scope class and try to call $user = Auth::user() inside apply function

Most helpful comment

You can use hasUser method in Auth class.

class UserVisibilityScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder $builder
     * @param  \Illuminate\Database\Eloquent\Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        if( Auth::hasUser() ) {

            $user = Auth::user();

            // some constrains based on the $user object
        }
    }
}

All 11 comments

Can you provide a code snippet to reproduce the error? Preferably we can copy/paste to get the same problem.

It sounds like you're calling Auth::user() outside a request context, similar to how you cannot call it in a controller constructor, but without code it's hard to say for sure.

Hi there, thank you for response.
Here is my scope:

class UserVisibilityScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder $builder
     * @param  \Illuminate\Database\Eloquent\Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $user = Auth::user();

        // some constrains based on the $user object
    }
}

I added this global scope in model User like this:

protected static function boot()
{
        parent::boot();
        static::addGlobalScope(new UserVisibilityScope);
}

Which caused error 500 reported after long loading of page directly from nginx.

However, when i modified method boot this way:

protected static function boot()
{
        parent::boot();
        static::addGlobalScope(new UserVisibilityScope(Auth::user()));
}

It started working!

You do have use Auth; in the UserVisibilityScope file? How are you using the scope?

Your 500 error should contain more information we can use for debugging. If it doesn't check your logs for more details and stack traces.

@slavokozar Is it possible that you're running into a stack overflow issue?

The flow should be something like this:

  1. Auth::user() looks for a User model by running a query like this:
    User::where('id', session('user_id'))->first().
  2. UserVisibilityScope will be applied to this query since it's a global scope.
  3. While running UserVisibilityScope@apply(), Auth::user() gets called again.
  4. The whole thing is repeated until you reach a stack overflow.

On the other hand, if you call Auth::user() in the User::boot() method, its query would be resolved without applying UserVisibilityScope since this scope is not yet a global scope at that point of the execution.

@kevinnio I think that's exactly my case.
Since I called Auth::user() in User:boot() method before applying my UserVisibilityScope result of Auth::user() is allready cached and is not queried again.

Thank you for explaining that for me.

DO you guys have idea how to solve this problem with a different approach? In my app i am querying list of users on many different places so I decided to use a global query.

Can you post the actual UserVisibilityScope code?

No problem.

namespace App\Scopes;

use App\Classes\GroupRoles;
use App\Classes\SchoolRoles;
use App\Models\Users\Group;
use App\Models\Users\School;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;

class UserVisibilityScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder $builder
     * @param  \Illuminate\Database\Eloquent\Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $userObj = Auth::user();

        // admin can see anybody
        if (Auth::check() && !$userObj->isAdmin()) {

            // schools, in which logged user has admin or teacher role
            $schools = $userObj->schools()->wherePivotIn('role', [SchoolRoles::ADMIN, SchoolRoles::TEACHER])->pluck(School::TABLE_NAME . '.id');

            // groups, in which logged user has teacher role
            $groups = $userObj->groups()->wherePivot('role', GroupRoles::TEACHER)->pluck(Group::TABLE_NAME . '.id');

            $builder->where(function($query) use ($schools, $groups){
                if($schools->count() > 0)

                    $query->whereHas('schools', function ($query) use ($schools) {
                        $query->where(School::TABLE_NAME . '.id', $schools);
                    });

                if($groups->count() > 0)
                    $query->orWhereHas('groups', function ($query) use ($groups) {
                        $query->whereIn(Group::TABLE_NAME . '.id', $groups);
                    });
            });
        }
    }
}

Actually global scopes made me more problems, than solutions so I changed my scoles into local.
Thank you guys, for explaining this problem to me. I will close this thread, because I think original problem was solved.

You can use hasUser method in Auth class.

class UserVisibilityScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder $builder
     * @param  \Illuminate\Database\Eloquent\Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        if( Auth::hasUser() ) {

            $user = Auth::user();

            // some constrains based on the $user object
        }
    }
}

I faced this same issue on a laravel 5.8 setup. Fixed it by creating a middleware, add applicable scope to model and then apply middleware directly in controller.

As a n example, applying country scope to User model goes thus:

Create UserCountryScope.php middleware
In the handle method, apply your scope

    public function handle($request, Closure $next)
    {
        if(!auth()->check()) {
            User::addGlobalScope('country', function (Builder $builder) {
                $country = session('country');
                $builder->where('country',$country);
            });
        }
        return $next($request);
    }

Now, in any controller you which to apply the scope (perhaps just for specific methods), specify the UserCountryScope middleware within the controllers constructor.

    public function __construct()
    {
        $this->middleware('userCountryScope')->only(['listings']); //This applies it only to listings method in this case
    }

Don't forget to register the alias in your applications route middleware in app/Http/Kernel.php

    protected $routeMiddleware = [
       ....
        'userCountryScope' => \App\Http\Middleware\UserCountryScope::class,
   ];

Applying scopes this way works best for me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

YannPl picture YannPl  路  3Comments

JamborJan picture JamborJan  路  3Comments

PhiloNL picture PhiloNL  路  3Comments

gabriellimo picture gabriellimo  路  3Comments

felixsanz picture felixsanz  路  3Comments