英文:
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
- 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() 时,都会进行相同的数据库查询。
- 我曾考虑从控制器传递这个,但要传递给所有控制器吗?
- 我还考虑在基本控制器 Controller.php 中添加构造函数,但对 auth()->user() 得到 NULL。
- 也许使用下面的方式,但当我尝试传递 $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
- 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.
- I was thinking about passing this from Controller, but to all Controllers?
- I also thought to add constructor to base controller: Controller.php but getting NULL for auth()->user()
- 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论