Laravel策略源代码分析

策略类的源代码,还是在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()方法,剩余的流程在另外的文档分析中,已经有讲解。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容