skip to Main Content

How to check if user is Admin or not in laravel blade view without calling DB multiple times?
Is there a way how to pass it in generic way to all views?

I have relationships, middleware and all defined. This is DB structure:

user table:

  • id
  • email
  • name

role table:

  • id
  • name

user_role table:

  • id
  • user_id
  • role_id

Note: DB structure has to be like that, I dont want to have to have IsAdmin flag on user table as I am not planning to add new column everytime new role is added.

Currently what I am doing in views is pretty basic:

View:

@if(auth()->user()->IsAdministrator()))
    ..
@endif

User model:

public function isAdministator() 
{
   return $this->roles()->where('role_id', 1)->exists();
}
public function roles()
{
    return $this->belongsToMany(Role::class);
}

Which is fine and it is working, but the problem is, when I need to do this multiple times on the page:

  • to show Edit button only to Admins
  • to make Delete button visible only to Admins
  • in navigation menu, to show specific menu items only for Admins

Everytime I am checking for IsAdministrator(), it is making a DB querry for same thing.

  1. I was thinking about passing this from Controller, but to all Controllers?
  2. I also thought to add contructructor to base controller: Controller.php but getting NULL for auth()->user()
  3. Perhaps using below, but it is not working as I am getting NULL as well, when I am trying to pass $IsAdmin = auth()-user()-IsAdministrator() https://laravel.com/docs/10.x/views#sharing-data-with-all-views

Is there good practice on this?

Note 2: same question was asked by user w1n78, but it was lost in between other answers: https://stackoverflow.com/a/47082188/8009914

2

Answers


  1. In eloquent in Laravel, accessing query builders as so $this->roles() will always create a SQL query to the database when that code is called.

    If you utilize the relationship loading that eloquent automatically does, it will not perform multiple queries.

    // one query
    $this->roles->where('role_id', 1)->first();
    $this->roles->where('role_id', 1)->first();
    $this->roles->where('role_id', 1)->first();
    
    // three queries
    $this->roles()->where('role_id', 1)->first();
    $this->roles()->where('role_id', 1)->first();
    $this->roles()->where('role_id', 1)->first();
    

    The only downside is with the query builder you get the power of SQL functions. With the eloquent relationship approach you are limited to the Collection class representing an array of relationship models in Laravel.

    Login or Signup to reply.
  2. Instead of using auth()->user() -> ... in your view multiple times, define a variable and pass it to your views:

    $user = auth()->user();
    
    return view('...', ['user' => $user]);
    

    Note: This can be done in a Controller to return to a Single view, or in View Composer for all views, etc.

    This will allow you to call $user->isAdministrator() in your views. Currently, this is the same as your existing code, but we can now make this more performant, since the User and it’s relationships can be loaded into a variable.

    First, load your roles relationship:

    $user = auth()->user();
    
    $user->load('roles');
    
    return view('...', ['user' => $user]);
    

    Now, $user->roles can be accessed without needing an additional query. As pointed out correctly by mrhn, $user->roles() will ALWAYS execute a new query, regardless if the Relationship has been loaded or not.

    In your isAdministrator() method, adjust your code:

    public function isAdministrator() {
      if (!$this->relationLoaded('roles')) { 
        throw new Exception('Roles relationship not loaded');
        // Or return `false`, etc.; up to you on how to handle this
      }
    
      return $this->roles->first(function ($role) { 
        return $role->id === 1;
      });
    
      // https://laravel.com/docs/10.x/collections#method-first
      // `->first()` will return a `Role` instance, or `null`, which is "truthy" enough
    
      // Or, tack on `? true : false`, or cast to `(bool)`, etc.
    
      // return $this->roles->first(function ($role) { 
      //   return $role->id === 1;
      // }) ? true : false;
    
      // return (bool)$this->roles->first(function ($role) { 
      //   return $role->id === 1;
      // });
    }
    

    Now, in your view, check your method via:

    @if($user->isAdministrator())
      // Handles `true` or `false` from method, without having to call any additional queries
    @endif
    

    If all is done properly, you should be able to call $user->isAdministrator(), and/or other ->is{Whatever}() methods multiple times without trigging any additional queries.

    Sidenote, in later versions of Laravel, you can force this behaviour by triggering an Exception when you try to access a relationship that isn’t loaded:

    https://laravel.com/docs/10.x/eloquent-relationships#preventing-lazy-loading

    If you included that code, you would be able to remove the if (!$this->relationLoaded('role')) { ... } section of your isAdministrator() method, as it would be handled by the Application globally.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search