前面我們看完了驗證(Authenticate)的實作,今天來看授權(Authorization)。
從官方文件可以大概知道它的主要角色有兩個:Gate 和 Policy。Policy 並沒有特定的介面定義,甚至也可以用 Closure 取代,而 Gate 有。從 Gate 延伸出來的 UML 圖如下:
@startuml
interface Illuminate\Contracts\Auth\Access\Authorizable
interface Illuminate\Contracts\Auth\Access\Gate
interface Illuminate\Contracts\Auth\Authenticatable
class Access\Gate
class Access\Response
class Illuminate\Foundation\Auth\Access\Authorizable
Illuminate\Contracts\Auth\Access\Gate <|.. Access\Gate
Illuminate\Contracts\Auth\Access\Gate -> Illuminate\Contracts\Auth\Authenticatable
Illuminate\Foundation\Auth\Access\Authorizable -> Illuminate\Contracts\Auth\Access\Gate
Illuminate\Contracts\Auth\Access\Authorizable <|.. Illuminate\Foundation\Auth\Access\Authorizable
Access\Gate -> Access\Response
@enduml
建構的過程也寫在 AuthServiceProvider:
$this->app->singleton(GateContract::class, function ($app) { return new Gate($app, function () use ($app) { return call_user_func($app['auth']->userResolver()); }); });
|
Facade 是 Gate:
class Gate extends Facade { protected static function getFacadeAccessor() { return GateContract::class; } }
|
這次的類別關係,並沒想像中的錯綜複雜,但似乎也是一個需要花時間理解的元件。
了解 Gate
參考官方文件的範例:
Gate::define('update-post', function ($user, $post) { return $user->id == $post->user_id; });
Gate::define('update-post', 'PostPolicy@update');
if (Gate::allows('update-post', $post)) { }
if (Gate::denies('update-post', $post)) { }
Gate::forUser($user)->allows('update-post', $post)); Gate::forUser($user)->denies('update-post', $post));
|
後面會先以 Closure 定義法分析。開始來看這一系列的程式碼是如何運作的。首先看 define()
:
public function define($ability, $callback) { if (is_callable($callback)) { $this->abilities[$ability] = $callback; } elseif (is_string($callback)) { $this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback); } else { throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string."); }
return $this; }
|
判斷有沒有授權,會使用 allows()
與 denies()
:
public function allows($ability, $arguments = []) { return $this->check($ability, $arguments); }
public function denies($ability, $arguments = []) { return ! $this->allows($ability, $arguments); }
|
很好懂,就不說明了。再來看 check()
:
public function check($abilities, $arguments = []) { return collect($abilities)->every(function ($ability) use ($arguments) { try { // 呼叫 raw() 取得結果 return (bool) $this->raw($ability, $arguments); } catch (AuthorizationException $e) { return false; } }); }
|
raw()
的原始碼:
public function raw($ability, $arguments = []) { $arguments = Arr::wrap($arguments);
$user = $this->resolveUser();
$result = $this->callBeforeCallbacks( $user, $ability, $arguments );
if (is_null($result)) { $result = $this->callAuthCallback($user, $ability, $arguments); }
return $this->callAfterCallbacks( $user, $ability, $arguments, $result ); }
|
依續看三種 callback 做了什麼事:
protected function callBeforeCallbacks($user, $ability, array $arguments) { $arguments = array_merge([$user, $ability], [$arguments]);
foreach ($this->beforeCallbacks as $before) { if (! $this->canBeCalledWithUser($user, $before)) { continue; }
if (! is_null($result = $before(...$arguments))) { return $result; } } }
protected function callAuthCallback($user, $ability, array $arguments) { $callback = $this->resolveAuthCallback($user, $ability, $arguments);
return $callback($user, ...$arguments); }
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; }
if (isset($this->abilities[$ability]) && $this->canBeCalledWithUser($user, $this->abilities[$ability])) { return $this->abilities[$ability]; }
return function () { return null; }; }
protected function callAfterCallbacks($user, $ability, array $arguments, $result) { foreach ($this->afterCallbacks as $after) { if (! $this->canBeCalledWithUser($user, $after)) { continue; }
$afterResult = $after($user, $ability, $result, $arguments);
$result = $result ?? $afterResult; }
return $result; }
|
感覺關鍵都在 canBeCalledWithUser()
是否回傳 true
protected function canBeCalledWithUser($user, $class, $method = null) { if (! is_null($user)) { return true; }
if (! is_null($method)) { return $this->methodAllowsGuests($class, $method); }
return $this->callbackAllowsGuests($class); }
protected function methodAllowsGuests($class, $method) { try { $reflection = new ReflectionClass($class);
$method = $reflection->getMethod($method); } catch (Exception $e) { return false; }
if ($method) { $parameters = $method->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]); }
return false; }
protected function callbackAllowsGuests($callback) { $parameters = (new ReflectionFunction($callback))->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]); }
protected function parameterAllowsGuests($parameter) { return ($parameter->getClass() && $parameter->allowsNull()) || ($parameter->isDefaultValueAvailable() && is_null($parameter->getDefaultValue())); }
|
回到一開始的範例:
Gate::define('update-post', function ($user, $post) { return $user->id == $post->user_id; });
if (Gate::allows('update-post', $post)) { }
|
這裡的 allows()
將會在上述 resolveAuthCallback()
取得 define()
的 callback,再依 callback 的結果決定 allows()
回傳 bool。
了解了上面的流程後,forUser()
就非常好懂了:
public function forUser($user) { $callback = function () use ($user) { return $user; };
return new static( $this->container, $callback, $this->abilities, $this->policies, $this->beforeCallbacks, $this->afterCallbacks ); }
|
產生一個新的 Gate 實例,只是 user resolver 換掉而已。所有 abilities 等屬性,都跟原本的無異。
以上,Gate 使用 Closure 的流程大概分析完畢,明天再看 Policy 類別是怎麼串接上去的。