文件位置:Illuminate\Auth\Access\Gate
allows(),check(),inspect(),raw() 这四个成员方法都是public 封装属性,可以在外部直接调用。
成员方法 | 作用 |
---|---|
allows | 确定是否应为当前用户授予给定的能力 |
check | 确定是否应为当前用户授予所有给定的能力 |
inspect | 检查用户的给定能力 |
raw | 从授权回调中获取原始结果 |
使用上述四个方法,检查用户是否有相关操作授权按。研究代码发现,上述四个方法,属于一脉相承型的。放在一起研究是最合适的了。
这个图显示了,从allows()方法开始,内部的程序流程走向,最终将在raw()方法内部终结,并向返回结果。
现在将从allows()方法开始,学习整体底层代码流程。可以看到,allows()方法内部调用check()方法实现的。
public function allows($ability, $arguments = [])
{
return $this->check($ability, $arguments);
}
然后在check()方法内部,先变成集合对象,然后通过every()方法,内部执行inspect()方法,最终得到Response对象,执行allowed()方法,返回一个布尔值。
public function check($abilities, $arguments = [])
{
return collect($abilities)->every(function ($ability) use ($arguments) {
return $this->inspect($ability, $arguments)->allowed();
});
}
分析代码:
check方法,可以检查单独的授权动作,也可以批量检查授权,这块有两种用法
Gate::check("addMember")
或者批量检查多个授权操作,这时候要用数组类型传值
Gate::check(["addMember","editMember"]);
然后代码里 collect() 函数会生成一个集合对象Collection (Illuminate\Support\Collection) ,它的结构如下:
Illuminate\Support\Collection Object
(
[items:protected] => Array
(
[0] => "addMember"
[1] => "editMember"
)
)
集合类中的every()方法,它有三个传数,如果像现在程序中这样,只传入一个参数,并且参数类型为匿名函数。那么every()方法将先遍历items这个数组,将数组中每个元素(授权动作的名字)作为参数,放入到这个匿名函数中去运行。 如果其中有任意一个运行结果为false时,将终止遍历操作。
只有全部的授权都返回true时,最终every()方法才会返回true.
现在看一下check方法中,传入every()里的匿名函数。
function ($ability) use ($arguments) {
return $this->inspect($ability, $arguments)->allowed();
}
在every()方法中,将运行这个匿名函数,$ability 就是注册的授权动作addMember,editMember
这个函数执行后,其中运行的就是下面这句
return $this->inspect($ability, $arguments)->allowed();
$this->inspect($ability, $arguments) 将得到一个Response对象(Illuminate\Auth\Access\Response)
Response对象调用allowed()方法 ,得到布尔值,是true 或 false
接下来继续看inspect()方法
inspect()方法分析
inspect方法 直接使用时,返回Response对象(Illuminate\Auth\Access\Response)
它内部会先调用raw()方法,通过raw()方法找到注册授权时,addMember 对应的回调函数,然后运行函数,得到结果并返回给inspect()方法
如果当初Gate::define()注册授权的时候
raw() 返回两种类型:
第1种,如果注册时,像下面这样,将得到Response对象
public function boot()
{
$this->registerPolicies();
Gate::define("addMember",function($user){
return $user->id == 1 ?
Response::allow("ok,你可以操作"): Response::deny("你没有权限");
});
}
第2种就是普通的返回布尔值,inspect()方法将根据布尔值,实例化不同的Response对象。
源代码:
public function inspect($ability, $arguments = [])
{
try {
//找到授权动作对应的处理方法,并返回运行结果
$result = $this->raw($ability, $arguments);
//如果result是一个 Response类实例化对象,将直接返回
if ($result instanceof Response) {
return $result;
}
//$result 是布尔值,根据结果调用 Response中的方法
return $result ? Response::allow() : Response::deny();
} catch (AuthorizationException $e) {
return $e->toResponse();
}
}
inspect()方法整体还是很容易理解的,主要是raw()方法,内部流程的线索比较多。现在看最重要的raw()方法
raw()方法
这可以独立使用 Gate::raw() 获得指定授权的响应结果。
我总结它内部的运行,分成下面几个部分:
(1)先检查参数2 如果是字符串,就将它转化成数组。
(2)调用 $this->resolveUser() 得到当前登陆用户对应的Eloquent 模型对象 ,这样就可以掌握当前登陆者的各方面信息
(3)是寻找注册的授权动作,得到对应的回调函数的运行结果。
现在分析raw()源代码:
public function raw($ability, $arguments = [])
{
//转化成数组形式
$arguments = Arr::wrap($arguments);
//得到当前登陆用户的Eloquent 模型对象
$user = $this->resolveUser();
$result = $this->callBeforeCallbacks(
$user, $ability, $arguments
);
if (is_null($result)) {
$result = $this->callAuthCallback($user, $ability, $arguments);
}
return $this->callAfterCallbacks(
$user, $ability, $arguments, $result
);
}
这里主要讲$this->resolveUser()的执行过程,raw()方法其余代码在这个链接里有分析。
https://www.jianshu.com/p/b6e9a4ccb221
第一部分:检查参数类型
这块使用了Laravel定义的数组类 Arr,它的文件位置:Illuminate/Collections/Arr.php
wrap()方法很简单,判断是不是数组类型,如果不是转化成数组。
//转化成数组
$arguments = Arr::wrap($arguments);
第二部分:获取用户的Eloquent模型
$user = $this->resolveUser();
首先说明一下,我现在使用的Laravel8里,用户认证这块是使用的默认机制,在 config/auth.php中,pc端用的是web守卫模式,对应的用户模型类文件就是 App\Models\User.php 是使用的Eloquent类型。
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
通过resolveUser() 得到当前登陆用户的Eloquent 模型对象
方法代码如下:
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
以回调函数的形式,调用$this->userResolver.
这个userResolver是Gate类属性,在当初注册绑定Gate类时,传入了一个匿名函数。
function () use ($app) {
return call_user_func($app['auth']->userResolver());
});
所以此时运行这个匿名函数,实际上就是执行
return call_user_func($app['auth']->userResolver());
其中 $app['auth']->userResolver() 这句代码里
$app['auth'] 是 new AuthManager($app) 对象,
而AuthManager类里的userResolver方法代码如下:
public function userResolver()
{
return $this->userResolver;
}
在AuthManager类构造函数里,给 $this->userResolver 作了初始化,userResolver 也是一个匿名函数。
function ($guard = null) {
return $this->guard($guard)->user();
}
因此最后将执行
$this->guard($guard)->user();
$this->guard($guard) 将返回对应当前用户的模型类,例如PC端默认使用的是SessionGuard ,最后调用user()方法,得到当前登陆用户的Eloquent模型对象。
第二部分的分析结束,这块只要是看过Auth认证的底层代码逻辑,就比较明白,否则可能会觉得层级太深,感觉很绕。
第三部分:获取注册授权时,对应的回调函数
第三部分,查找授权指定的回调函数时,其实是分了三步进行。
因为Gate模块提供了一个叫授权拦截的功能。我先来说明一下这个什么叫拦截。
可以通过 Gate::before() 和 Gate:after() 注册前置和后置授权。
如果使用了上述两个方法后,raw()方法内部就会先检查 有没有Gate::before()操作,如果有这个操作,并且函数运行后,返回true,剩余的程序流程,就将不执行了。
如果没有Gate::before() ,才会执行 $this->callAuthCallback() 找到注册授权对应的回调函数。
当$this->callAuthCallback()操作结束后,检查有没有Gate::after()后置授权,如果存在,检查一下返回结果。
$result = $this->callBeforeCallbacks(
$user, $ability, $arguments
);
if (is_null($result)) {
$result = $this->callAuthCallback($user, $ability, $arguments);
}
return $this->callAfterCallbacks(
$user, $ability, $arguments, $result
);
关于before()和after()的操作,我单独放到了下面的链接
https://www.jianshu.com/p/b6e9a4ccb221
$this->callAuthCallback()部分的代码分析,放到了下面这个链接。