这篇文档中,学习如何找到当初授权时定义的回调函数,主要过程是分析raw方法调用的$this->callAuthCallback() 方法。
在raw()方法中,这句代码调用callAuthCallback()方法。得到一个执行结果
if (is_null($result)) {
$result = $this->callAuthCallback($user, $ability, $arguments);
}
callAuthCallback()方法会运行 授权回调函数,返回执行结果。
但在此之前,需要通过resolveAuthCallback()这个方法,找到对应的授权回调函数。
从下面的代码可以看到,resolveAuthCallback()负责找回调函数,callAuthCallback()方法只要运行回调函数,返回结果就可以了。
protected function callAuthCallback($user, $ability, array $arguments)
{
//得到回调函数
$callback = $this->resolveAuthCallback($user, $ability, $arguments);
//运行回调函数,返回执行结果
return $callback($user, ...$arguments);
}
接下来最重要的,就是学习 resolveAuthCallback方法。看一下它是如何找到授权按回调函数的。
resolveAuthCallback()
先看一个它的三个参数,ability 是注册授权动作,$arguments 数组类型,是传递进来的参数。
protected function resolveAuthCallback($user, $ability, array $arguments)
{
if (isset($arguments[0]) &&
! is_null($policy = $this->getPolicyFor($arguments[0])) &&
$callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
return $callback;
}
if (isset($this->stringCallbacks[$ability])) {
[$class, $method] = Str::parseCallback($this->stringCallbacks[$ability]);
if ($this->canBeCalledWithUser($user, $class, $method ?: '__invoke')) {
return $this->abilities[$ability];
}
}
if (isset($this->abilities[$ability]) &&
$this->canBeCalledWithUser($user, $this->abilities[$ability])) {
return $this->abilities[$ability];
}
return function () {
//
};
}
代码由三个if语句块构成。如果三种if语句都没有命中,最后将回一个空的匿名函数。 如果命中其中某一个if语句,最后将返回匿名函数。
三个if语句分别对应一种专门的注册授权的方法。
(1) 针对使用策略类注册的授权
(2)Gate::define() 注册时,参数2使用数组或字符串
(3)Gate::define() 注册时,参数2使用匿名函数
第一部分
使用策略类方式进行注册授权,并在执行策略检查时,像下面这样使用,就会命中第一部分的代码。
Gate::check("wujin",['lafenfen']);
参数1,wujin是在策略类中定义的方法名。
参数2, 必须使用数组类型,第1个元素必须是策略类的别名。记住是别名。
现在看代码,if里有三个条件,它们之间的关系是&&,必须三个条件都成立。
if (isset($arguments[0]) &&
! is_null($policy = $this->getPolicyFor($arguments[0])) &&
$callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
return $callback;
}
分析代码
(1) $arguments[0]
数组首元素,就是注册策略类时使用的别名。必须要存在。
(2) $this->getPolicyFor($arguments[0])
根据策略类别名,在类$this->policies属性里,找到真正的注册策略类名。
然后通过容器的make()方法解析策略类,并返回对象。
(3) $callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy))
在策略类对象$policy中,存在$ability方法,并证实它是合法的可调用的结构时,将返回一个自定义的匿名函数
当以上三个条件都成立时。就会返回这个得到的回调函数。
关于这块的详细的分析说明,我把它拆分出来,和Gate策略篇底层代码分析放到了一块,点击链接跳转。
https://www.jianshu.com/p/c0e37ceaf89d
第二部分
当我们注册授权时,参数2使用数组或字符串时。会命中第二部分代码
Gate::define('addMember', [AdminPolicy::class, 'wujin']);
或
Gate::define("addMember",AdminPolicy::class);
代码:
if (isset($this->stringCallbacks[$ability])) {
[$class, $method] = Str::parseCallback($this->stringCallbacks[$ability]);
if ($this->canBeCalledWithUser($user, $class, $method ?: '__invoke')) {
return $this->abilities[$ability];
}
}
分析:
存储在$this->stringCallbacks 中的内容有两种
第1种通过@符做连接符,连接策略类名和方法名
第2种就是一个单独的类名(类里必须要有__invoke()魔术方法)
(1)$this->stringCallbacks[$ability] =AdminPolicy@'wujin'
(2)$this->stringCallbacks[$ability] =AdminPolicy
使用Str::parseCallback($this->stringCallbacks[$ability]) ,以@符作分隔符,将字符串转换为数组。
$class 对应策略类名 $method对应方法名
如果不存在$method时,就会使用 __invoke来替换。
在通过canBeCalledWithUser()测试,当前用户是否可以执行,结果为true时、
就可以返回$this->abilities[$ability]注册的回调函数了。
if ($this->canBeCalledWithUser($user, $class, $method ?: '__invoke')) {
return $this->abilities[$ability];
}
第三部分
这部分的代码如下
if (isset($this->abilities[$ability]) && $this->canBeCalledWithUser($user, $this->abilities[$ability])) {
return $this->abilities[$ability];
}
当注册授权时,define()方法中传入匿名函数时,就会走这块流程
Gate::define("lafenfen",function($user,$id){
return $id==1;
});
分析代码:
if语句中,需要同时满足两个条件。
第1个 $this->abilities[$ability] 要有注册的授权内容
第2个 $this->canBeCalledWithUser($user, $this->abilities[$ability]) 当前用户有执行此方法的权限。
满足上述两个条件后将注册授权的回调函数返回。
return $this->abilities[$ability];
如果上述三种情况都不存在时,直接返回一个空的匿名函数。
return function () {
//
};
到此resolveAuthCallback()方法分析结束。在学习的过程中,使用的几个其它函数,放在下面进行说明。
canBeCalledWithUser()
作用:确定是否可以用给定的用户调用回调/方法
返回值:boolean
protected function canBeCalledWithUser($user, $class, $method = null)
{
//如果$user模型对像不为空时,直接返回true
if (! is_null($user)) {
return true;
}
if (! is_null($method)) {
return $this->methodAllowsGuests($class, $method);
}
if (is_array($class)) {
$className = is_string($class[0]) ? $class[0] : get_class($class[0]);
return $this->methodAllowsGuests($className, $class[1]);
}
return $this->callbackAllowsGuests($class);
}