Laravel策略源代碼分析

策略類的源代碼,還是在Illuminate\Auth\Access\Gate.php 類文件中定義的.

1、關于注冊策略類的運行流程

先說一下注冊策略類時發(fā)生的流程。當在AuthServiceProvider 服務提供者中,手動操作$policies 屬性執(zhí)行注冊操作后。
boot()方法中有一個registerPolicies()方法。

laravel框架的生命周期流程中,有一塊會執(zhí)行所有的服務提供者類,如果類里面有boot()方法時,就會執(zhí)行boot()方法中的內(nèi)容。

class AuthServiceProvider extends ServiceProvider
{
  
    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
        "lafen" => AdminPolicy::class,
    ];

    public function boot()
    {
        $this->registerPolicies();
    }
}

現(xiàn)在看一下registerPolicies()方法里的代碼,它被定義在Illuminate\Foundation\Support\Providers\AuthServiceProvider 類里。

代碼很簡單 policies()方法將返回 $this->policies 屬性,它就是文檔開頭介紹的,AuthServiceProvider 服務提供者類里的屬性。

public function registerPolicies()
    {
       //遍歷數(shù)組,$key 就是定義的數(shù)組鍵名,$value就是注冊的策略類。
        foreach ($this->policies() as $key => $value) {
            Gate::policy($key, $value);
        }
    }

而Gate::policy()方法的代碼如下所示,將策略類 注冊到policies屬性中。它是Gate類里的屬性,是一個數(shù)組類型。

    public function policy($class, $policy)
    {
        $this->policies[$class] = $policy;

        return $this;
    }

而且可以在控制器程序里,直接使用 Gate類中的policy()方法注冊策略類

Gate::policy("lafen",AdminPolicy::class);

好的,現(xiàn)在策略注冊流程就講完了。

2、如何獲取注冊的策略類

在控制器中,可以在check()方法的第二個參數(shù)中,數(shù)組首元素,填加注冊時用的自定義鍵名。內(nèi)部流程會執(zhí)行找到策略類和對應的方法,這個下面會有詳細說明。

public function indexAction()
{
    //wujin就是策略類里,我定義的方法。
    $result=Gate::check("wujin",["lafen"]);

    var_dump($result);
            
}

另一種就是可以通過getPolicyFor()方法, 返回策略類對象。

$obj=Gate::getPolicyFor("lafen");

前面演示的check()方法,它能找到策略類是因為內(nèi)部調(diào)用了raw()方法.

然后在Gate::raw()方法中會調(diào)用 callAuthCallback() 方法,緊接著會調(diào)用resolveAuthCallback() 執(zhí)行解析回調(diào)函數(shù)操作。

    protected function callAuthCallback($user, $ability, array $arguments)
    {
       //返回一個匿名函數(shù)
        $callback = $this->resolveAuthCallback($user, $ability, $arguments);
        //運行匿名函數(shù),返回結果
        return $callback($user, ...$arguments);
    }
現(xiàn)在以這個使用法舉例,走resolveAuthCallback()方法的底層代碼。
$result=Gate::check("wujin",["lafen"]);

上述用法將會觸發(fā)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] 數(shù)組首元素有值
 (2) $this->getPolicyFor($arguments[0])  返回解析的策略類對象
(3)   resolvePolicyCallback()  從策略類里,找到指定的授權方法,然后返回。

現(xiàn)在看第2個條件,也就是getPolicyFor()的代碼,下面的代碼,我們只要看前三段有注釋的內(nèi)容就行了。

public function getPolicyFor($class)
    {
       //如果$class是一個對象時,得到對應的類名
        if (is_object($class)) {
            $class = get_class($class);
        }
       //如果$class不是字符串,直接返回空
        if (! is_string($class)) {
            return;
        }
       //根據(jù)$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);
            }
        }
    }

上面的方法執(zhí)行到第三步時,找到注冊的策略類后,執(zhí)行resolvePolicy()方法,解析策略類,并返回策略類對象。
下面是通過容器的make()方法執(zhí)行解析。

   public function resolvePolicy($class)
    {
        return $this->container->make($class);
    }

現(xiàn)在看第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部分中

首先執(zhí)行 formatAbilityToMethod()檢查參$ability,看它是否有-符號
    protected function formatAbilityToMethod($ability)
    {
        return strpos($ability, '-') !== false ? Str::camel($ability) : $ability;
    }

此時$ability 就是策略類里的一個方法名,is_callable()檢查這個方法是否可以被調(diào)用,如果不能執(zhí)行調(diào)用,返回false

第二部分,直接返回一個匿名函數(shù),結束函數(shù)操作。就是下面這四行代碼,這個匿名函數(shù)最后會在callAuthCallback()方法中被運行。
分析一下代碼:

首先會調(diào)用callPolicyBefore()方法,它的作用和Gate::before()是一樣的,屬于提前攔截操作,如果在注冊的策略類中,
存在before方法時,程序會提前運行這個方法。

$result = $this->callPolicyBefore(
                $policy, $user, $ability, $arguments
);
如果結果不為空,則返回這個結果。
if (! is_null($result)) {
    return $result;
}

//如果沒有定義before方法,就根據(jù)$ability找到自定義的方法。先檢查$ability名字中是否有-符號。
$method = $this->formatAbilityToMethod($ability);

//檢查完畢后,執(zhí)行callPolicyMethod()方法,
return $this->callPolicyMethod($policy, $method, $user, $arguments);

callPolicyMethod()方法,將執(zhí)行策略類里的定義的方法。

我在Gate::check("wujin",["lafen"]) 中 定義的方法名是wujin,它對應的參數(shù)是$method

    protected function callPolicyMethod($policy, $method, $user, array $arguments)
    {
       //先將$arguments 數(shù)組 首元素移除。就是對應["lafen"],只保留其余的參數(shù)。
        if (isset($arguments[0]) && is_string($arguments[0])) {
            array_shift($arguments);
        }
        //檢查wujin這個方法是否可以被執(zhí)行調(diào)用
        if (! is_callable([$policy, $method])) {
            return;
        }
      //最后檢查這個策略類和方法,是否為當前$user用戶可以調(diào)用。如果滿足條件,就可以執(zhí)行wujin這個方法,然后返回運行結果。
        if ($this->canBeCalledWithUser($user, $policy, $method)) {
            return $policy->{$method}($user, ...$arguments);
        }
    }

在callAuthCallback()方法中,運行結束,返回執(zhí)行結果給raw()方法,剩余的流程在另外的文檔分析中,已經(jīng)有講解。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容