認可処理であるPolicyのソースコードリーディング（middleware編）


## ポリシーのソースコードリーディング

がんばりましょう。

## Gateファサード

解決されるクラス  `Illuminate/Auth/Access/Gate.php`

## ポリシーの登録

ポリシーのマッピングは`app/Providers/AuthServiceProvider`に記述します。

keyに関連するモデル、valueにポリシーを記述します。

```php:app/Providers/AuthServiceProvider
protected $policies = [
    // 'App\Model' => 'App\Policies\ModelPolicy',
]
```

`boot()`の中で呼ばれている`registerPolicies()`で登録していきます。

```php:Illuminate/Foundation/Support/Providers/AuthServiceProvider.php
public function registerPolicies()
    {
        foreach ($this->policies() as $key => $value) {
            Gate::policy($key, $value);
        }
    }
```

`$this->policies()`は先程の`policies`プロパティを返します。

`Illuminate/Auth/Access/Gate::policies()`が呼ばれます。

```php:Illuminate/Auth/Access/Gate.php
public function policy($class, $policy)
{
    $this->policies[$class] = $policy;

    return $this;
}
```

`Illuminate/Auth/Access/Gate`の`policies`プロパティに同じ形で値を格納していきます。

これにてポリシーの登録は終了。

## ポリシーをつかう

主なポリシーの使用方法は３つで、

- User.phpから使う
- Controllerから使う
- ミドルウェアで使う

中身は大体同じなので、今回はミドルウェアで使うを例に見ていきます。

### 想定ルート、ミドルウェア

```php
Route::get('/{book}/{comment}', 'BookController@index')->middleware('can:view,book,comment');
```

実際にアクセスされたURL

```php
/1/2
```

`book`はルートモデルバインディングで解決され、`comment`は解決されないものとします。

ここでの注意点は、`middleware()`内に書いた文字列の`book`や`comment`に当たる部分は必ず同じ名前でURIに記載してください。

たとえば、

```php
Route::get('/{book}/hoge', 'BookController@index')->middleware('can:view,book,comment');
```

のように、`middleware()`内に`comment`と書いてあるのに、`get`の第一引数の中に`{comment}`が含まれていないと怒られます。

ポリシーはリソース（モデル）に対しての認可を提供するので、ルートモデルバインディングで解決されたインスタンスを利用します。（なのでミドルウェアの順番も重要です）

### 想定ポリシー

```php
protected $polices = [
    Book::class => BookPolicy::class
]
```

B
`BookPolicy::view()`が定義されている。

### みていこう

まずは`'can:view,book,comment'`がどのようにミドルウェアに変換されるか見ていきましょう。

ミドルウェアをごにょごにょしているのはここです。

```php:Illuminate/Pipeline/Pipeline.php
protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            [$name, $parameters] = $this->parsePipeString($pipe);

            $pipe = $this->getContainer()->make($name);

            $parameters = array_merge([$passable, $stack], $parameters);
        
            $carry = method_exists($pipe, $this->method)
                            ? $pipe->{$this->method}(...$parameters)
                            : $pipe(...$parameters);

            return $this->handleCarry($carry);
        };
    };
}
```

`$pipe`には`'can:view,book'`という文字列が入り、`$passable`にはRequestクラス、`$stack`は次のミドルウェアが入っています。

`parsePipeString()`でミドルウェア名とその他に分けます。

```php:Illuminate/Pipeline/Pipeline.php
protected function parsePipeString($pipe)
{
    [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);

    if (is_string($parameters)) {
        $parameters = explode(',', $parameters);
    }

    return [$name, $parameters];
}
```

`$name = 'can'`
`$parameters = ['view', 'book', 'comment']`

`$this->getContainer()->make($name)`で`Illuminate/Auth/Middleware/Authorize.php`インスタンスを生成します。
(文字列とミドルウェアの対応は`app/Http/Kernel.php`に書いてあります。)

`$this->method`には`handle`が入っているので、`Illuminate/Auth/Middleware/Authorize::handle()`が実行されます.


```php:Illuminate/Auth/Middleware/Authorize.php
public function handle($request, Closure $next, $ability, ...$models)
{
    $this->gate->authorize($ability, $this->getGateArguments($request, $models));

    return $next($request);
}
```

`$ability`に`view`が入り、`...$models`に残りもの（`book` `comment`）が渡されます。

まずは`getGateArguments()`を見ていきましょう。

```php:Illuminate/Auth/Middleware/Authorize.php
protected function getGateArguments($request, $models)
{
    if (is_null($models)) {
        return [];
    }

    return collect($models)->map(function ($model) use ($request) {
        return $model instanceof Model ? $model : $this->getModel($request, $model);
    })->all();
}
```

コレクションクラスの`map()`で一つずつ処理をしていきます。

今回`$models`は文字列なので`getModel()`が呼ばれます。

```php:Illuminate/Auth/Middleware/Authorize.php
protected function getModel($request, $model)
{
    if ($this->isClassName($model)) {
        return trim($model);
    } else {
        return $request->route($model, null) ?:
            ((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
    }
}
```

`isClassName`で完全修飾名かどうかを判定しています。完全修飾名だとそのまま返すようです。

完全修飾名出ない場合を見ていきましょう。

```php:Illuminate/Http/Request.php
public function route($param = null, $default = null)
{
    $route = call_user_func($this->getRouteResolver());

    if (is_null($route) || is_null($param)) {
        return $route;
    }

    return $route->parameter($param, $default);
}
```

`$route`には現在のルートの情報を保持するRouteクラスが入ってきます。

Routeクラスの`parameters`プロパティにはルートモデルバインディングで解決されたモデルなどが入っています。

```php
protected $parameters = [
    'book' => 解決されたBookモデル,
    'comment' => 2,
```

`$route->parameter()`は第一引数をkeyに`$parameters`から値を取得します。

今回の`getGateArguments()`の返り値は配列になります。中身は[解決されたBookモデル, 2]です。

`$this->gate->authorize('view', [解決されたBookモデル, 2])`を見てきます。

```php:Illuminate/Auth/Access/Gate.php
public function authorize($ability, $arguments = [])
{
    return $this->inspect($ability, $arguments)->authorize();
}
```

`inspcet`が呼ばれて、

```php:Illuminate/Auth/Access/Gate.php
public function inspect($ability, $arguments = [])
{
    try {
        $result = $this->raw($ability, $arguments);

        if ($result instanceof Response) {
            return $result;
        }

        return $result ? Response::allow() : Response::deny();
    } catch (AuthorizationException $e) {
        return $e->toResponse();
    }
}
```

`raw`が呼ばれ、

```php:Illuminate/Auth/Access/Gate.php
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
    );
}
```

`Arr:wrap`は配列でないときは配列でラップしてくれるだけです。

`$user`にはログイン済みのユーザーが入ってきます。

`callBeforeCallbacks()`と`callAfterCallbacks()`は、それぞれ`Gate::before()`、`Gate::after()`で定義したコールバックが呼ばれます。

今回は関係ないので`callAuthCallback()`を見ていきます。

```php:Illuminate/Auth/Access/Gate.php
protected function callAuthCallback($user, $ability, array $arguments)
{
    $callback = $this->resolveAuthCallback($user, $ability, $arguments);

    return $callback($user, ...$arguments);
}
```

`resolveAuthCallback`は...

```php:Illuminate/Auth/Access/Gate.php
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;
    }

    //...
}
```

`$arguments`は[解決されたBookモデル, 2]なので、issetはtrueになり、`getPolicyFor()`は、

```php:Illuminate/Auth/Access/Gate.php
public function getPolicyFor($class)
{
    if (is_object($class)) {
        $class = get_class($class);
    }

   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);
        }
    }
}
```

ポリシーは

```php
protected $polices = [
    Book::class => BookPolicy::class
]
```

と登録したので、`$this->policies[$class]`の返り値は`BookPolicy::class`になります。

`resolvePolicy`でクラス名からインスタンスを生成します。`resolveAuthCallback()`に戻ります。

```php:Illuminate/Auth/Access/Gate.php
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;
    }
    
    //...
}
```

`resolvePolicyCallback()`はコールバックを返すみたいなのでコールバックが呼ばれるときに見てみることにします。

呼び出し元に戻ります。

```php:Illuminate/Auth/Access/Gate.php
protected function callAuthCallback($user, $ability, array $arguments)
{
    $callback = $this->resolveAuthCallback($user, $ability, $arguments);

    return $callback($user, ...$arguments);
}
```

コールバックを呼び出してその返り値を返しているようです。案外登場が早かったのでコールバックの中身を見てみましょう。


```php:Illuminate/Auth/Access/Gate.php
protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
{

    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);
    };
}
```

`$user`はログインユーザー、`$ability`は`view`、`$argument`は[解決されたBookモデル, 2]、`$policy`はさっき解決したBookPolicyインスタンス。

ポリシーにも`before()`がありますが今回は関係ないのでスルー。

```php:Illuminate/Auth/Access/Gate.php
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;
    }

    if ($this->canBeCalledWithUser($user, $policy, $method)) {
        return $policy->{$method}($user, ...$arguments);
    }
}
```

`$arguments[0]`が文字列ならばここで抹消されます。

最終的に`$policy->{$method}($user, ...$arguments)`でポリシー内のメソッドが呼ばれます。ポリシーのメソッドはboolを返します。

なので、`callAuthCallback()`の返り値は、ポリシーの指定されたメソッドの返り値ということになります。

```php:Illuminate/Auth/Access/Gate.php
public function raw($ability, $arguments = [])
{
    $arguments = Arr::wrap($arguments);

    $user = $this->resolveUser();

    if (is_null($result)) {
        $result = $this->callAuthCallback($user, $ability, $arguments);
    }

    return $this->callAfterCallbacks(
        $user, $ability, $arguments, $result
    );
}
```

`after`を指定していないので、`callAuthCallback()`の返り値がそのまま返ります。

```php:Illuminate/Auth/Access/Gate.php
public function inspect($ability, $arguments = [])
{
    try {
        $result = $this->raw($ability, $arguments);

        if ($result instanceof Response) {
            return $result;
        }

        return $result ? Response::allow() : Response::deny();
    } catch (AuthorizationException $e) {
        return $e->toResponse();
    }
}
```

`$result`にポリシーのメソッドの返り値が格納されます。

```php:Illuminate/Auth/Access/Response.php
public static function deny($message = null, $code = null)
{
    return new static(false, $message, $code);
}

public static function allow($message = null, $code = null)
{
    return new static(true, $message, $code);
}
```

`Illuminate/Auth/Access/Response.php`を生成して返していますね。

```php:Illuminate/Auth/Access/Gate.php
public function authorize($ability, $arguments = [])
{
    return $this->inspect($ability, $arguments)->authorize();
}
```

`inspct()`は`Illuminate/Auth/Access/Response`を返すので、`Illuminate/Auth/Access/Response::authorize()`が呼ばれます。

```php:Illuminate/Auth/Access/Response.php
public function authorize()
{
    if ($this->denied()) {
        throw (new AuthorizationException($this->message(), $this->code()))
                    ->setResponse($this);
    }

    return $this;
}
```

ここでエラーレスポンスを返すか、次のミドルウェアへ処理を移すかの分岐になっていますね。

以上でポリシーの認可は終わりです！

## まとめ

ポリシーはリソース（モデル）を利用するGateの糖衣のようなもの。
ルートモデルバインディングを前提としている。
before, afterなどのhookが用意されている。