How to check user is Admin in laravel blade view without calling DB multiple times

huangapple go评论58阅读模式
英文:

How to check user is Admin in laravel blade view without calling DB multiple times

问题

Sure, here's the translated portion of your text:

如何在Laravel Blade视图中检查用户是否是管理员,而不需要多次调用数据库?
是否有一种通用的方法将其传递到所有视图中?

我已经定义了关系、中间件等等。这是数据库结构:

user 表格:

  • id
  • email
  • name

role 表格:

  • id
  • name

user_role 表格:

  • id
  • user_id
  • role_id

注意:数据库结构必须保持这样,我不想在用户表格上添加IsAdmin标志,因为我不打算每次添加新角色时都添加新列。

目前我在视图中的操作非常基本:

视图:

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

用户模型:

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

这很好,而且它正常工作,但问题是,当我需要在页面上多次执行此操作时:

  • 仅向管理员显示编辑按钮
  • 仅向管理员显示删除按钮
  • 在导航菜单中,仅为管理员显示特定菜单项

每次检查IsAdministrator() 时,都会进行相同的数据库查询。

  1. 我曾考虑从控制器传递这个,但要传递给所有控制器吗?
  2. 我还考虑在基本控制器 Controller.php 中添加构造函数,但对 auth()->user() 得到 NULL。
  3. 也许使用下面的方式,但当我尝试传递 $IsAdmin = auth()->user()->isAdministrator() 时,也得到 NULL:https://laravel.com/docs/10.x/views#sharing-data-with-all-views

这方面有什么好的做法吗?

注意2:同样的问题被用户w1n78提出过,但在其他答案之间被遗漏了:https://stackoverflow.com/a/47082188/8009914

英文:

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 query for same thing.

  1. I was thinking about passing this from Controller, but to all Controllers?
  2. I also thought to add constructor 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

答案1

得分: 2

在Laravel的Eloquent中,通过$this->roles()这样访问查询构建器时,每次调用该代码都会创建一个SQL查询到数据库。

如果你使用Eloquent自动执行的关系加载,它将不会执行多个查询。

// 一个查询
$this->roles->where('role_id', 1)->first();
$this->roles->where('role_id', 1)->first();
$this->roles->where('role_id', 1)->first();

// 三个查询
$this->roles()->where('role_id', 1)->first();
$this->roles()->where('role_id', 1)->first();
$this->roles()->where('role_id', 1)->first();

唯一的不足之处是,使用查询构建器,你可以使用SQL函数的功能。而使用Eloquent关系方法,你只能限于Laravel中表示关系模型数组的Collection类。

英文:

In Eloquent in Laravel, accessing query builders as so $this->roles() will always create an 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.

答案2

得分: 1

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 its 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 triggering any additional queries.

Sidenote, in later versions of Laravel, you can force this behavior 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.

英文:

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.

答案3

得分: 0

你可以考虑使用 spatie/once 来确保在请求期间只发生一次查找。

function isAdministrator(): bool
{
    return once(fn () => $this->roles()->where('role_id', 1)->exists());
}

一个策略也可能是一个不错的地方,但我认为它也不提供你所期望的缓存行为。如果你在使用 Laravel Nova,once 已经为你安装好了。

英文:

You could consider using spatie/once to ensure the lookup only occurs once during a request.

function isAdministrator(): bool
{
    return once(fn () => $this->roles()->where('role_id', 1)->exists());
}

A policy might also be a good place for this, but I don't think it provides any caching behaviour like you're after either. If you're using Laravel Nova once is already installed for you

huangapple
  • 本文由 发表于 2023年6月1日 04:51:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76377180.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定