策略类的源代码,还是在Illuminate\Auth\Access\Gate.php 类文件中定义的.
1、关于注册策略类的运行流程
先说一下注册策略类时发生的流程。当在AuthServiceProvider 服务提供者中,手动操作$policies 属性执行注册操作后。
boot()方法中有一个registerPolicies()方法。
laravel框架的生命周期流程中,有一块会执行所有的服务提供者类,如果类里面有boot()方法时,就会执行boot()方法中的内容。
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
"lafen" => AdminPolicy::class,
];
public function boot()
{
$this->registerPolicies();
}
}
现在看一下registerPolicies()方法里的代码,它被定义在Illuminate\Foundation\Support\Providers\AuthServiceProvider 类里。
代码很简单 policies()方法将返回 $this->policies 属性,它就是文档开头介绍的,AuthServiceProvider 服务提供者类里的属性。
public function registerPolicies()
{
//遍历数组,$key 就是定义的数组键名,$value就是注册的策略类。
foreach ($this->policies() as $key => $value) {
Gate::policy($key, $value);
}
}
而Gate::policy()方法的代码如下所示,将策略类 注册到policies属性中。它是Gate类里的属性,是一个数组类型。
public function policy($class, $policy)
{
$this->policies[$class] = $policy;
return $this;
}
而且可以在控制器程序里,直接使用 Gate类中的policy()方法注册策略类
Gate::policy("lafen",AdminPolicy::class);
好的,现在策略注册流程就讲完了。
2、如何获取注册的策略类
在控制器中,可以在check()方法的第二个参数中,数组首元素,填加注册时用的自定义键名。内部流程会执行找到策略类和对应的方法,这个下面会有详细说明。
public function indexAction()
{
//wujin就是策略类里,我定义的方法。
$result=Gate::check("wujin",["lafen"]);
var_dump($result);
}
另一种就是可以通过getPolicyFor()方法, 返回策略类对象。
$obj=Gate::getPolicyFor("lafen");
前面演示的check()方法,它能找到策略类是因为内部调用了raw()方法.
然后在Gate::raw()方法中会调用 callAuthCallback() 方法,紧接着会调用resolveAuthCallback() 执行解析回调函数操作。
protected function callAuthCallback($user, $ability, array $arguments)
{
//返回一个匿名函数
$callback = $this->resolveAuthCallback($user, $ability, $arguments);
//运行匿名函数,返回结果
return $callback($user, ...$arguments);
}
现在以这个使用法举例,走resolveAuthCallback()方法的底层代码。
$result=Gate::check("wujin",["lafen"]);
上述用法将会触发resolveAuthCallback()中第一种判断条件。
//下面展示的这一种,就是判断是否有对应的注册策略类。
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;
}
在第1个if结构中,有三个条件,全部满足后,就能得到策略类里的对应方法。
(1) $arguments[0] 数组首元素有值
(2) $this->getPolicyFor($arguments[0]) 返回解析的策略类对象
(3) resolvePolicyCallback() 从策略类里,找到指定的授权方法,然后返回。
现在看第2个条件,也就是getPolicyFor()的代码,下面的代码,我们只要看前三段有注释的内容就行了。
public function getPolicyFor($class)
{
//如果$class是一个对象时,得到对应的类名
if (is_object($class)) {
$class = get_class($class);
}
//如果$class不是字符串,直接返回空
if (! is_string($class)) {
return;
}
//根据$class 在$this->policies 中找到对应的注册策略类名,返回解析后的对象。
if (isset($this->policies[$class])) {
return $this->resolvePolicy($this->policies[$class]);
}
foreach ($this->guessPolicyName($class) as $guessedPolicy) {
if (class_exists($guessedPolicy)) {
return $this->resolvePolicy($guessedPolicy);
}
}
foreach ($this->policies as $expected => $policy) {
if (is_subclass_of($class, $expected)) {
return $this->resolvePolicy($policy);
}
}
}
上面的方法执行到第三步时,找到注册的策略类后,执行resolvePolicy()方法,解析策略类,并返回策略类对象。
下面是通过容器的make()方法执行解析。
public function resolvePolicy($class)
{
return $this->container->make($class);
}
现在看第3个条件,resolvePolicyCallback()
resolvePolicyCallback()
源代码:
protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
{
if (! is_callable([$policy, $this->formatAbilityToMethod($ability)])) {
return false;
}
return function () use ($user, $ability, $arguments, $policy) {
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
if (! is_null($result)) {
return $result;
}
$method = $this->formatAbilityToMethod($ability);
return $this->callPolicyMethod($policy, $method, $user, $arguments);
};
}
代码很清晰,分成了两部分,第1部分中
首先执行 formatAbilityToMethod()检查参$ability,看它是否有-符号
protected function formatAbilityToMethod($ability)
{
return strpos($ability, '-') !== false ? Str::camel($ability) : $ability;
}
此时$ability 就是策略类里的一个方法名,is_callable()检查这个方法是否可以被调用,如果不能执行调用,返回false
第二部分,直接返回一个匿名函数,结束函数操作。就是下面这四行代码,这个匿名函数最后会在callAuthCallback()方法中被运行。
分析一下代码:
首先会调用callPolicyBefore()方法,它的作用和Gate::before()是一样的,属于提前拦截操作,如果在注册的策略类中,
存在before方法时,程序会提前运行这个方法。
$result = $this->callPolicyBefore(
$policy, $user, $ability, $arguments
);
如果结果不为空,则返回这个结果。
if (! is_null($result)) {
return $result;
}
//如果没有定义before方法,就根据$ability找到自定义的方法。先检查$ability名字中是否有-符号。
$method = $this->formatAbilityToMethod($ability);
//检查完毕后,执行callPolicyMethod()方法,
return $this->callPolicyMethod($policy, $method, $user, $arguments);
callPolicyMethod()方法,将执行策略类里的定义的方法。
我在Gate::check("wujin",["lafen"]) 中 定义的方法名是wujin,它对应的参数是$method
protected function callPolicyMethod($policy, $method, $user, array $arguments)
{
//先将$arguments 数组 首元素移除。就是对应["lafen"],只保留其余的参数。
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
//检查wujin这个方法是否可以被执行调用
if (! is_callable([$policy, $method])) {
return;
}
//最后检查这个策略类和方法,是否为当前$user用户可以调用。如果满足条件,就可以执行wujin这个方法,然后返回运行结果。
if ($this->canBeCalledWithUser($user, $policy, $method)) {
return $policy->{$method}($user, ...$arguments);
}
}
在callAuthCallback()方法中,运行结束,返回执行结果给raw()方法,剩余的流程在另外的文档分析中,已经有讲解。