概要
前回の続き。
今回は、ログインユーザに管理者権限を与えて、管理者専用画面を表示する。
データベース
ER図
今回、DBは以下のようにする。
管理者クーポンが発行されたユーザが管理者権限を持つとする。
マイグレーション
以下のコマンドでマイグレーションファイルを作成できる。
php artisan make:migration create_coupons_table
上記で作成したファイルに含まれる$table->timestamps();
はnullableなcreated_at
とupdated_at
を作成する。
DBに作ってもらいたかったので、作成時、更新時の時刻を挿入する設定にしている。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCouponsTable extends Migration
{
public function up()
{
Schema::create('coupons', function (Blueprint $table) {
$table->string('id');
$table->integer('type')->default(config('const.Coupons.TYPE_GET', 1))->comment('1:取得, 2:使用');
$table->string('name');
$table->boolean('is_display')->default(true);
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
});
}
public function down()
{
Schema::dropIfExists('coupons');
}
}
クーポン種別は定数で定義してみた。
<?php
return [
// Couponsで使う定数
'Coupons' => [
'TYPE_GET' => 1,
'TYPE_USE' => 2,
],
];
ユーザとクーポンの紐づけテーブルでは外部キーを設定し、
ユーザテーブルやクーポンテーブルにないものは登録できないようにした。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserCouponsTable extends Migration
{
public function up()
{
Schema::create('user_coupons', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('coupon_id')->comment('クーポンID');
$table->integer('subscribe_user_id')->unsigned()->comment('受取ユーザID');
$table->integer('publish_user_id')->unsigned()->comment('発行ユーザID');
$table->dateTime('expire')->nullable()->comment('利用期限');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
$table->softDeletes();
$table->foreign('subscribe_user_id')->references('id')->on('users');
$table->foreign('publish_user_id')->references('id')->on('users');
$table->foreign('coupon_id')->references('id')->on('coupons');
});
}
public function down()
{
Schema::dropIfExists('user_coupons');
}
}
php artisan migarete
でデータベース更新。
シーダの設定
php artisan make:seeder UsersTableSeeder
システム用のユーザと初期ユーザを作成。
<?php
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
public function run()
{
$user = [
'id' => 1,
'firebase_uid' => 'system',
'name' => 'システム管理者',
'twitter_screen_name' => '',
'twitter_profile_image_url_https' => '',
];
DB::table('users')->insert($user);
$user = [
'id' => 2,
'firebase_uid' => env('FIRST_USER_FIREBASE_UID'),
'name' => env('FIRST_USER_NAME'),
'twitter_screen_name' => env('FIRST_USER_TWITTER_SCREEN_NAME'),
'twitter_profile_image_url_https' => env('FIRST_USER_TWITTER_PROFILE_IMAGE_URL'),
];
DB::table('users')->insert($user);
}
}
管理者クーポンを追加。
<?php
use Illuminate\Database\Seeder;
use App\Enums\Coupon\CouponIds;
class CouponsSeeder extends Seeder
{
public function run()
{
$coupon = [
'id' => CouponIds::ADMIN(),
'name' => '管理者クーポン',
'is_display' => false,
];
DB::table('coupons')->insert($coupon);
}
}
クーポンIDはEnumで登録してみた。
composer require myclabs/php-enum
<?php
namespace App\Enums\Coupon;
use MyCLabs\Enum\Enum;
class CouponIds extends Enum
{
const ADMIN = 'admin';
}
システムユーザから初期ユーザに向けて管理者クーポンを発行
<?php
use Illuminate\Database\Seeder;
use App\Enums\Coupon\CouponIds;
class UserCouponsSeeder extends Seeder
{
public function run()
{
$user_coupon = [
'id' => 1,
'coupon_id' => CouponIds::ADMIN(),
'subscribe_user_id' => 2,
'publish_user_id' => 1,
];
DB::table('user_coupons')->insert($user_coupon);
}
}
実行するSeederを指定
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
// $this->call(UsersTableSeeder::class);
$this->call([
UserSeeder::class,
CouponsSeeder::class,
UserCouponsSeeder::class,
]);
}
}
シーダの実行
php artisan db:seed
アプリケーション
モデルの設定
php artisan make:model Models/UserCoupon
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UserCoupon extends Model
{
protected $table = 'user_coupons';
}
subscribe_user_id
でユーザクーポンとユーザを紐づける。
<?php
namespace App;
use Laravel\Passport\HasApiTokens; // 追加
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Enums\Coupon\CouponIds;
class User extends Authenticatable
{
use Notifiable, HasApiTokens; // HasApiTokens を追加
public function __construct(array $attributes = []){
}
protected $fillable = [
'name', 'twitter_screen_name','twitter_profile_image_url_https', 'firebase_uid'
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
+ public function userCoupons(){
+ return $this->hasMany('App\Models\UserCoupon', 'subscribe_user_id');
+ }
}
ゲートの設定
<?php
namespace App\Gate;
use App\User;
use App\Enums\Coupon\CouponIds;
final class AdminAccess
{
public function __invoke(User $user): bool
{
// 管理者用のクーポンを持っているかDBに問い合わせる。
return $user->userCoupons()->where('coupon_id',CouponIds::ADMIN())->exists();
}
}
上記で設定したゲートをadmin-access
の名前で登録する。
<?php
namespace App\Providers;
use App\Auth\MySessionGuard;
use App\Auth\MyEloquentUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Auth;
use App\Gate\UserAccess;
use App\Gate\AdminAccess;
use \Psr\Log\LoggerInterface;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
public function boot(LoggerInterface $logger)
{
$this->registerPolicies();
Auth::provider('my_eloquent', function($app, array $config) {
return new MyEloquentUserProvider($app['hash'], $config['model']);
});
+ // 認可
+ Gate::define('admin-access', new AdminAccess);
// 認可の前にロギング
Gate::before(function ($user, $ability) use ($logger) {
// Log::info("Hello my log,");
$logger->info($ability, ['firebase_uid'=>$user->getAuthIdentifier()]);
});
}
}
ルーティング
Route::group(['middleware' => ['auth', 'can:admin-access']], function () {
// この中は管理者権限の場合のみルーティングされる
Route::get('/admin', function (Illuminate\Http\Request $request) {
return view('admin/dashboard');
});
});
管理者ページへのリンク
管理者権限があるときだけ、管理者ページへのリンクを表示。
@extends('layouts.app')
@section('title') マイページ @endsection
@section('head-scripts')
@if(Auth::check())
<script src="https://www.gstatic.com/firebasejs/7.2.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.2.0/firebase-auth.js"></script>
<script src="{{ mix('js/home/index.js') }}"></script>
@endif
@endsection
@section('content')
<main role="main" class="container">
<div class="starter-template">
<h1>マイページ</h1>
@if(Auth::check())
<ul>
+ @can('admin-access')
+ <li><a href="/admin">管理者画面へ</a></li>
+ @endcan
<li><a href="#" id="logout">ログアウト</a></li>
</ul>
@else
こんにちは! ゲストさん <br />
<a href="/login">ログイン</a>
@endif
</div></main>
@endsection
参考
Laravel で定数をつかうよ
Laravel で Enum を使う
Laravel 6.0 Artisan コンソール
全 68 種類!Laravel 5.6 の artisan コマンドまとめ
【メモ】【Laravel】外部キー制約付き Migrate がさっぱり動かないときのチェック・ポイント(Mysql)
Laravel の DB migration で日付のデフォルトを指定
Laravel(Eloquent)の save メソッドを使ったら MySQL の timestamp 型で謎な挙動が発生した話
管理者クーポンによるタグ画面の制御
blade テンプレートでの切替
Laravel 6.0 基本のタスクリスト
laravel
readouble laravel
【Laravel】 認証や認可に関する補足資料
Laravel 6.0 認可
Laravel 6.0 ルーティング
Laravel 6.0 ミドルウェア
Laravel の Gate(ゲート)機能で権限(ロール)によるアクセス制限を実装する
Laravel 6.0 バリデーション