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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容