AUTOVICE

TECH BLOG

独自ポリシーの導入手順と注意点(ハマリポイント)を解説

2020-03-04

はじめに

ユーザーと記事を関連付けておいて、編集や削除は記事の作成者のみ許可したいときにはポリシーを作成します。今回はポリシーの導入手順と注意点(ハマリポイント)を解説します。

導入手順

ポリシー作成

ターミナルで以下のコマンドを実行します。

$ php artisan make:policy ArticlePolicy
Policy created successfully.

作成されたapp/Policies/ArticlePolicy.phpを以下のように設定します。

<?php

namespace App\Policies;

use App\User;
use App\Article;

class ArticlePolicy
{
    public function view(User $user, Article $article)
    {
        return $user->id === $article->user_id;
    }
}

view関数を定義し、ポリシーの真偽値を返り値に設定します。今回はログインユーザーと記事の作成者が一致しているかを判定しています。

ポリシーとモデルの関連付け

作成したポリシーとモデルを関連付けます。app/Providers/AuthServiceProvider.phpを以下のように設定します。

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Article;
use App\Policies\ArticlePolicy;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Article::class => ArticlePolicy::class,
    ];
}

Articleモデルを操作するときはArticlePolicyポリシーを適用させるように設定しています。

ポリシーの適用

コントローラーにポリシーを適用します。今回は記事の作成者のみ編集と削除を許可したいので、editupdatedestroyにポリシーを適用させます。

app/Http/Controllers/ArticlesController.phpを以下のように設定します。

    public function __construct()
    {
        $this->middleware('can:view,article', ['only' => ['edit', 'update', 'destroy']]);
    }

★ハマリポイント1
middleware関数の第1引数ですが、'can:view, article'のようにカンマの後にスペースを入れるとポリシーにうまく引数が渡せないので注意してください。

うまくいかない場合

ポリシーの作成から導入まで行ったにもかかわらず、実際に動かしてみると、記事の作成者にもかかわらず編集や削除ができないことがあります。記事の作成者でhttp://localhost:8000/articles/1/editにリクエストしても、403ページにリダイレクトされてしまう状況です。

★ハマリポイント2
結論から言うと、DBから取得した値のデータ型が一致していないのが原因です。

app/Policies/ArticlePolicy.phpをもう一度見てみます。

    public function view(User $user, Article $article)
    {
        return $user->id === $article->user_id;
    }

===比較演算子を使っています。これは、両辺の値および型が一致している場合のみtrueとなる比較演算子です。値は一致しているはずなので、型が一致していないのではないかと推測できます。

実際にDBから取得した型がどうなっているか調べてみます。

$ php artisan tinker
>>> gettype(App\User::first()->id);
=> "integer"
>>> gettype(App\Article::first()->user_id);
=> "string"

Articleモデルのuser_idの型がstringになっています。なぜこのような型変換が起こるのかというと、PHPのMySQLドライバの設定が関連しているようなのですが、要はPHPの仕様ということのようです。

型変換が起こって型が不一致となっていることはわかりました。これを解消するために、モデルのcastsプロパティを設定します。

app/Article.phpを以下のように設定します。

class Article extends Model
{
    protected $casts = [
        'user_id' => 'integer',
    ];
}

これでuser_idintegerで取得してくれるようになります。

$ php artisan tinker
>>> gettype(App\Article::first()->user_id);
=> "integer"

integerで取得できてますね。

まとめ

記事中のハマリポイントは、実際に私がハマったところです😅ハマリポイント2に関しては、Laravelの開発チームもPHPの仕様を理解した上で、castsプロパティを作ったのではないかと思います。