繼續昨天,來看 Policy 怎麼串接的。
一樣是那個範例:
Gate::define('update-post', 'PostPolicy@update');
if (Gate::allows('update-post', $post)) { }
|
昨天有提到關鍵程式碼在 define()
裡呼叫的 buildAbilityCallback()
:
protected function buildAbilityCallback($ability, $callback) { return function () use ($ability, $callback) { if (Str::contains($callback, '@')) { [$class, $method] = Str::parseCallback($callback); } else { $class = $callback; }
$policy = $this->resolvePolicy($class);
$arguments = func_get_args();
$user = array_shift($arguments);
$result = $this->callPolicyBefore( $policy, $user, $ability, $arguments );
if (! is_null($result)) { return $result; }
return isset($method) ? $policy->{$method}(...func_get_args()) : $policy(...func_get_args()); }; }
|
因為本身是包裝成 Closure,所以後面的流程都跟原本 Closure 沒有差異。
另外一個會用到 Policy 的地方在 Support\Providers\AuthServiceProvider,它可以覆寫 policies 屬性,並使用 registerPolicies()
來註冊被定義的 Policy:
public function registerPolicies() { foreach ($this->policies as $key => $value) { Gate::policy($key, $value); } }
|
policy()
的任務單純是註冊 Policy:
public function policy($class, $policy) { $this->policies[$class] = $policy;
return $this; }
|
註冊的方法,官方的範例如下:
Gate::policy(Post::class, PostPolicy::class);
|
這個註冊方法,它不會在 abilities
屬性加東西,所以後面解析 callback 的流程會跟原本 callback 並不一樣,主要在 resolveAuthCallback()
下面這個片段程式碼:
if (isset($arguments[0]) && ! is_null($policy = $this->getPolicyFor($arguments[0])) && $callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) { return $callback; }
|
先來看看 getPolicyFor()
是怎麼取得 Policy 的:
public function getPolicyFor($class) { if (is_object($class)) { $class = get_class($class); }
if (! is_string($class)) { return; }
if (isset($this->policies[$class])) { return $this->resolvePolicy($this->policies[$class]); }
foreach ($this->policies as $expected => $policy) { if (is_subclass_of($class, $expected)) { return $this->resolvePolicy($policy); } } }
|
有了 Policy 的物件後,再來看看 resolvePolicyCallback()
是如何解析出 callback 的:
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); }; }
protected function formatAbilityToMethod($ability) { return strpos($ability, '-') !== false ? Str::camel($ability) : $ability; }
|
最後看看 callPolicyMethod()
是怎麼呼叫的
protected function callPolicyMethod($policy, $method, $user, array $arguments) { if (isset($arguments[0]) && is_string($arguments[0])) { array_shift($arguments); }
if (! is_callable([$policy, $method])) { return null; }
if ($this->canBeCalledWithUser($user, $policy, $method)) { return $policy->{$method}($user, ...$arguments); } }
|
回頭看官網的範例,假如有了一個 PostPolicy:
class PostPolicy { public function update(User $user, Post $post) { return $user->id === $post->user_id; } }
|
配合剛剛提到的設定,與呼叫 allows()
:
Gate::policy(Post::class, PostPolicy::class);
if (Gate::allows('update', $post)) { }
|
如此呼叫後,經過上面的分析,程式到了 resolveAuthCallback()
時的 $arguments 會長的像下面這樣:
而 getPolicyFor()
的參數將會是 $post,因此取到的 policy 實例會是 PostPolicy。緊接著 resolvePolicyCallback()
在確認 callable 時,傳入的參數將會是:
這是可以呼叫的 callable,所以會通過並產生 callback。確認目前的 user 是否能呼叫 canBeCalledWithUser()
,因為這次是明確的 class + method,所以會使用 methodAllowsGuests()
來確認是否 guest 能呼叫。
剩下的分析就與 Closure 定義的方法沒有什麼差異了。
明天會來分析,各種確認權限的方法,背後是如何實作的。