Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.8: How to resolve auth()->user() inside Global Scope's apply method?

I need to apply specific global scope only if authenticated user's role is equal to something. So that user with certain role will only be able to execute queries on a given subset of records.

I can easily deal with User model (of currently logged in user) but not inside scope's apply method.

https://github.com/laravel/framework/issues/22316#issuecomment-349548374

The scope constructor executes very early, before the auth middleware has run. Resolve the user within the apply method, not in the constructor.

OK, so I'm inside the apply method:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class UserScopeForSalesContactAdmin 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)
    {
        dd('within apply', auth()->user());
        dd(auth()->user()); // won't work

        $builder->where('foo', '=', 'something from currently logged in user');
    }
}

Obviously second dd gives:

Maximum function nesting level of '512' reached, aborting!

How to resolve this? I imagine I should call IOC container via app()->make() but then?

Thanks for any hints.

edit: I think I see what's causing the infinite loop (https://github.com/laravel/framework/issues/26113) but still I need to find a best way to obtain the User…

like image 436
Matt Komarnicki Avatar asked Jun 07 '19 03:06

Matt Komarnicki


People also ask

What is auth ()- User () in laravel?

Laravel includes built-in authentication and session services which are typically accessed via the Auth and Session facades. These features provide cookie-based authentication for requests that are initiated from web browsers. They provide methods that allow you to verify a user's credentials and authenticate the user.

What does Auth :: check () do?

Auth::check() defers to Auth::user() . It's been that way since as long as I can remember. In other words, Auth::check() calls Auth::user() , gets the result from it, and then checks to see if the user exists. The main difference is that it checks if the user is null for you so that you get a boolean value.


2 Answers

The method what you are looking for is exactly Auth::hasUser(). The method was added in Laravel 5.6 through my PR.

[5.6] Add ability to determine if the current user is ALREADY authenticated without triggering side effects by mpyw · Pull Request #24518 · laravel/framework

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class UserScopeForSalesContactAdmin 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() && Auth::user()->role === 'something') {
            $builder->where('foo', '=', 'something from currently logged in user');
        }
    }
}

Just call Auth::hasUser() to prevent Auth::user() from causing side effects.

like image 61
mpyw Avatar answered Nov 03 '22 01:11

mpyw


Introduction

Finally found a solution for this, I have been trying to fix it and looking for solutions for over an hour. The problem with what I have is that I am using a trait to apply the global scope on certain models and not all models. The list is big so I had to find a way to use auth()->check() in a global scope.

Solution

So in your model class or trait that is being used by your model class, you can listen to the RouteMatched event, which assures that the session provider is booted. Then add the global scope within the the event listener's closure/callable.

Code

use Illuminate\Routing\Events\RouteMatched;

/**
 * The "boot" method of the model.
 *
 * @return void
 */
protected static function boot()
{
    Event::listen(RouteMatched::class, function () {
        static::addGlobalScope('company', function ($query) {
            // auth()->check() will now return the correct value
            // Logic here
        });
    });
}
like image 43
Raed Yakoubi Avatar answered Nov 03 '22 01:11

Raed Yakoubi