策略類的源代碼,還是在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)有講解。