はじめに
ユーザーと記事を関連付けておいて、編集や削除は記事の作成者のみ許可したいときにはポリシーを作成します。今回はポリシーの導入手順と注意点(ハマリポイント)を解説します。
導入手順
ポリシー作成
ターミナルで以下のコマンドを実行します。
$ 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
ポリシーを適用させるように設定しています。
ポリシーの適用
コントローラーにポリシーを適用します。今回は記事の作成者のみ編集と削除を許可したいので、edit
、update
、destroy
にポリシーを適用させます。
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_id
をinteger
で取得してくれるようになります。
$ php artisan tinker
>>> gettype(App\Article::first()->user_id);
=> "integer"
integer
で取得できてますね。
まとめ
記事中のハマリポイントは、実際に私がハマったところです😅ハマリポイント2に関しては、Laravelの開発チームもPHPの仕様を理解した上で、casts
プロパティを作ったのではないかと思います。