LoginSignup
0
1

More than 1 year has passed since last update.

Laravelで作られた掲示板に機能を追加する③

Posted at

Laravelで作られた掲示板に機能を追加する②の続きです。

目標

  • アクセス制限を実装
    1. Laravel-permissionのインストール ~ seedの流し込み
    2. viewの調整
    3. 気が付いたエラーの修正

色々と出来そうで触ってみたいという理由から、laravel-permissionを用います。
現時点では、管理者と一般ユーザーだけの制御を考えています。

Role(役割) Permission(権限) 備考
admin admin_permission, user_permission すべてのユーザーの記事を変更可能
user user_permission ログインユーザーの記事を変更可能

参考サイト
https://qiita.com/Fell/items/7cd398b8ae65ac42950f
基本的なインストールはこのサイトを参考にしています。
seedの流し込みまでは結構端折っているので、一読するのをおすすめします。
エラーが出たところは解決のために記述を増やしているので、サイトと差異のある記述箇所があります。

その他の参考サイト
https://webty.jp/staffblog/production/post-3917/
https://reffect.co.jp/laravel/spatie-laravel-permission-package-to-use
https://econosys-system.com/blog/archives/342


Laravel-permissionのインストール ~ seedの流し込み

Laravel-permissionのインストール

$ composer require spatie/laravel-permission

二か所に追記

app/Http/Kernel.php
protected $routeMiddleware = [

    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, // ← 追加
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, // ← 追加
    'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, // ← 追加
]
app/Models/User.php
use Spatie\Permission\Traits\HasRoles; // ← 追加

class User extends Authenticatable
{
    use Notifiable,HasRoles; // ← 追加

マイグレーションファイルの生成とテーブル作成

$ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
$ php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
$ php artisan migrate


DBデータの作成

各シーダーのひな形作成を作り、データを流し込みます。

1. PermissionTableSeeder.php

シーダーのひな形作成

$ php artisan make:seeder PermissionTableSeeder

admminとuserを作成

database\seeders\PermissionTableSeeder.php
use Spatie\Permission\Models\Permission; // ← 追記
class PermissionTableSeeder extends Seeder
{
    public function run()
    {
        $permissions = [
            'admin_permission', // ← 全権限用
            'user_permission', // ← 一般権限用
        ];
        foreach ($permissions as $permission) {
            Permission::create(['name' => $permission]);
        }
    }
}

seederの実行

$ php artisan db:seed --class=PermissionTableSeeder

Permissonテーブルにadmin_permissionとuser_permissionが入りったので、興味のある方はDBを確認してみてください。


2. RoleTableSeeder.php

手順は同じなので説明を省きます。

$ php artisan make:seeder RoleTableSeeder
database/seeders/RoleTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role; // ← 追記

class RoleTableSeeder extends Seeder
{
    public function run()
    {
        $roles = [
            'admin',
            'user',
        ];
        foreach ($roles as $role) {
            Role::create(['name' => $role]);
        }
    }
}
$ php artisan db:seed --class=RoleTableSeeder


3. RoleHasPermissionTableSeeder.php

$ php artisan make:seeder RoleHasPermissionTableSeeder
database\seeders\RoleHasPermissionTableSeeder.php
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission; // ← 追記 意味なし?
use Spatie\Permission\Models\Role;

(use Illuminate\Support\Facades\Hash; // ← 追記)
(use App\Models\User; // ← 追記)

class RoleHasPermissionTableSeeder extends Seeder
{
    public function run()
    {
        // admin
        $permissions = [
            'admin_permission',
            'user_permission',
        ];
        $role = Role::findByName('admin');
        $role->givePermissionTo($permissions);

        // user
        $permissions = [
            'user_permission',
        ];
        $role = Role::findByName('user');
        $role->givePermissionTo($permissions);  
    }
}
$ php artisan db:seed --class=RoleHasPermissionTableSeeder


4. UserTableSeeder.php

以前作っていたので、変更になります。

database\seeders\UsersTableSeeder.php
<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash; // ← 追記

class UserTableSeeder extends Seeder
{
    public function run()
    {
        //追加
        // admin
        $user = User::create([
            'name'     => 'web管理責任者',
            'email'    => 'admin@gmail.com',
            'password' => Hash::make('admin'),
        ]);
        $user->assignRole('admin');

        // user1
        $user = User::create([
            'name'     => 'ユーザー1',
            'email'    => 'user1@gmail.com',
            'password' => Hash::make('user'),
        ]);
        $user->assignRole('user');

        // user2
        $user = User::create([
            'name'     => 'ユーザー2',
            'email'    => 'user2@gmail.com',
            'password' => Hash::make('user'),
        ]);
        $user->assignRole('user');


        // user3
        $user = User::create([
            'name'     => 'ユーザー3',
            'email'    => 'user3@gmail.com',
            'password' => Hash::make('user'),
        ]);
        $user->assignRole('user');


        // user4
        $user = User::create([
            'name'     => 'ユーザー4',
            'email'    => 'user4@gmail.com',
            'password' => Hash::make('user'),
        ]);
        $user->assignRole('user');
}

もしダミーデータのユーザー数が異なる場合は、下記の箇所を修正してください。
理由は、ダミーデータのユーザー数と合わせていないと、整合性が取れずにエラーが出るからです。

database\factories\PostFactory.php と database\factories\CommentFactory.php
    public function definition()
    {
        return [

            'user_id' => $this->faker->numberBetween(1,5), // ← 5となっている箇所を、ダミーデータのユーザー数に変更
        ];
    }

seedの読み込みでエラーが出た場合は、気にせずにDatabaseSeeder.phpに進んでください。

$ php artisan db:seed --class=UserTableSeeder


5. DatabaseSeeder.php

php artisan db:seed --class=なんちゃらでデータを流し込んでいたので、refreshやfreshのことを考え、DataBaseSeeder.phpにも書き込んでおきます。
追記は必ずUsersTableSeederの上にしてください。下に書いた場合は、DBの整合性が取れずにエラーになります。

database\seeders\DatabaseSeeder.php
    public function run()
    {
        $this->call(PermissionTableSeeder::class); // ← 追記
        $this->call(RoleTableSeeder::class); // ← 追記
        $this->call(RoleHasPermissionTableSeeder::class); // ← 追記
        $this->call(UsersTableSeeder::class);
        $this->call(PostsTableSeeder::class);
    }

(6. php artisan migrate:fresh --seed)

必要に応じて下記コマンドの入力を行ってください。
4. UserTableSeeder.phpのphp artisan db:seed --class=UserTableSeederでエラーが出た場合は、migrateをやり直すことでエラーの解決につながるかもしれません。

$ php artisan migrate:fresh --seed
とか
$ php artisan migrate:refresh --seed

【Laravel】migrate:refresh と migrate:fresh の違い

とりあえずこれでLaravel permissionのインストールとダミーデータの流し込みが終わりました。



[メモ]
自分の知識では、アクセス制御というのは、users tableにroleカラムを作り、role_idが1ならadmin, 2ならuserのようにするものだと思っていました。

id role_id
1 1
2 2
3 2
4 2
5 2

なので、どうしてlaravel permissionはusers tableにroleカラムを作らないのにアクセス制御が出来るのか不思議でした。

ポリモーフィックリレーションとかいうことをしてるとのことでした。
https://readouble.com/laravel/8.x/ja/eloquent-relationships.html



2. viewの編集

いい方法が思いつかなかったので、if文でhasrole('admin')とhasrole('user')の表示を分けました。
さらにuserの時は、@if( ( $post->user_id ) === ( Auth::user()->id ) )でidが一致していると編集と削除ボタンを表示します。

今後の課題として、もし必要になるならば、管理用のページとユーザー用のページを分けるとかになるかもしれません。

resources\views\bbs\index.blade.php
        @foreach ($posts as $post)
            <tr>
                <td>{{ $post->id }}</td>
                <td>{{ $post->category->name }}</td>
                <td>{{ $post->created_at->format('Y.m.d') }}</td>
                <td>{{ $post->user->name }}</td>
                <td>{{ $post->subject }}</td>
                <td>{!! nl2br(e(Str::limit($post->message, 100))) !!}
                @if ($post->comments->count() >= 1)
                    <p><span class="badge badge-primary">コメント:{{ $post->comments->count() }}件</span></p>
                @endif
                </td>
                <td class="text-nowrap">
                    <p><a href="{{ action('App\Http\Controllers\PostsController@show', $post->id) }}" class="btn btn-primary btn-sm">詳細</a></p>

                    @if (Route::has('login')) // ここから
                        @auth
                            @hasrole('admin')
                                <p><a href="{{ action('App\Http\Controllers\PostsController@edit', $post->id) }}" class="btn btn-info btn-sm">編集</a></p>
                                <p>
                                    <form method="POST" action="{{ action('App\Http\Controllers\PostsController@destroy', $post->id) }}">
                                        @csrf
                                        @method('DELETE')
                                        <button class="btn btn-danger btn-sm">削除</button>

                                    </form>
                                </p>
                            @endhasrole

                            @hasrole('user')
                                @if( ( $post->user_id ) === ( Auth::user()->id ) )
                                    <p><a href="{{ action('App\Http\Controllers\PostsController@edit', $post->id) }}" class="btn btn-info btn-sm">編集</a></p>
                                    <p>
                                        <form method="POST" action="{{ action('App\Http\Controllers\PostsController@destroy', $post->id) }}">
                                            @csrf
                                            @method('DELETE')
                                            <button class="btn btn-danger btn-sm">削除</button>
                                        </form>
                                    </p>
                                @endif
                            @endhasrole
                        @endauth
                    @endif // ここまで

                </td>
            </tr>
        @endforeach

編集・編集ボタンのところを分岐させます

resources\views\bbs\show.blade.php
         <!-- 編集・編集ボタン -->
        @if (Route::has('login'))
            @auth
            @hasrole('admin')
                <div class="mb-4 text-right">
                    <a href="{{ action('App\Http\Controllers\PostsController@edit', $post->id) }}" class="btn btn-info">
                    編集する
                    </a>

                    <form
                        style="display: inline-block;"
                        method="POST"
                        action="{{ action('App\Http\Controllers\PostsController@destroy', $post->id) }}"
                    >
                        @csrf
                        @method('DELETE')

                        <button class="btn btn-danger">削除する</button>
                    </form>
                </div>
            @endhasrole

            @hasrole('user')
            @if( ( $post->user_id ) === ( Auth::user()->id ) )
                <div class="mb-4 text-right">
                    <a href="{{ action('App\Http\Controllers\PostsController@edit', $post->id) }}" class="btn btn-info">
                    編集する
                    </a>

                    <form
                        style="display: inline-block;"
                        method="POST"
                        action="{{ action('App\Http\Controllers\PostsController@destroy', $post->id) }}"
                    >
                        @csrf
                        @method('DELETE')

                        <button class="btn btn-danger">削除する</button>
                    </form>
                </div>
            @endif
            @endhasrole
            @endauth
        @endif

メモ:あれこれアクセス制御を調べているとgateとpoliciyを知ったのでリンクを記載します。
https://qiita.com/nunulk/items/719e1d53c455946184ac
https://reffect.co.jp/laravel/laravel-gate-policy-understand
https://www.ritolab.com/entry/56


3. 気が付いたエラーの修正や気になる点の修正

投稿の新規作成時の名前入力欄の修正

赤枠が不要なのでviewから削除し、ログインしているユーザーのIDがDBに入るように変更します。
名前を入力して投稿するとエラーが出るので、この解決にもつながると思います。

スクリーンショット (44).png

削除箇所をわかりやすいようにコメントアウトしていますが、不必要なので削除してください。

resources\views\bbs\create.blade.php
                <!-- <div class="form-group">
                    <label for="subject">
                        名前
                    </label>
                    <input
                        id="name"
                        name="name"
                        class="form-control {{ $errors->has('name') ? 'is-invalid' : '' }}"
                        value="{{ old('name') }}"
                        type="text"
                    >
                    @if ($errors->has('name'))
                        <div class="invalid-feedback">
                            {{ $errors->first('name') }}
                        </div>
                    @endif
                </div> -->

続いてコントローラの修正をします。

app\Http\Requests\PostRequest.php
use Illuminate\Support\Facades\Auth; // ← 追記

    /**
     * バリデーション、登録データの整形など
     */
    public function store(PostRequest $request)
    {
        $id = Auth::id();
        $savedata = [
            'user_id' => $id, // ← 変更 'name' => $request->name,
            'subject' => $request->subject,
            'message' => $request->message,
            'category_id' => $request->category_id,
        ];

        $post = new Post;
        $post->fill($savedata)->save();

        return redirect('/bbs')->with('poststatus', '新規投稿しました');
    }

ログインしているユーザーのidを取るためにAuthを使うので、useを追記します。
取ってきたidを\$idに入れて、\$savedataのnameだったところを修正します。
下記のような書き方も出来るので、一応参考のため記載しておきます。

app\Http\Requests\PostRequest.php
    public function store(PostRequest $request)
    {
        $id = Auth::id();
        $post = new Post;
        $post->user_id = $id;
        $post->fill($request->all())->save();
    }

続いて、このままではバリデーションに引っ掛かるのでPostRequestを修正します。

app\Http\Requests\PostRequest.php
    public function rules()
    {
        return [
            // 'name' => 'required|max:40', // ← 削除
            'subject' => 'required|max:80',
            'message' => 'required|max:350',
            'category_id' => 'required|integer',
        ];
    }

    /**
     * エラーメッセージを日本語化
     * 
     */
    public function messages()
    {
        return [
            // 'name.required' => '名前を入力してください', // ← 削除
            // 'name.max' => '名前は40文字以内で入力してください', // ← 削除
            'subject.required' => '件名を入力してください',
            'subject.max' => '件名は80文字以内で入力してください',
            'message.required' => 'メッセージを入力してください',
            'message.max' => 'メッセージは350文字以内で入力してください',
            'category_id.required' => 'カテゴリーを選択してください',
            'category_id.integer' => 'カテゴリーの入力形式が不正です',
        ];
    }

$requestにはnameが入っていないので、メッセージと合わせて削除します。


投稿の編集の名前入力欄の修正

同様にこの修正も行います。

スクリーンショット (45).png

下記内容を削除で終了です。

resources\views\bbs\edit.blade.php
                <!-- <div class="form-group">
                    <label for="subject">
                        名前
                    </label>
                    <input
                        id="name"
                        name="name"
                        class="form-control {{ $errors->has('name') ? 'is-invalid' : '' }}"
                        value="{{ old('name') ?: $post->user->name }}"
                        type="text"
                    >
                    @if ($errors->has('name'))
                        <div class="invalid-feedback">
                            {{ $errors->first('name') }}
                        </div>
                    @endif
                </div> -->


投稿の詳細ページの名前入力欄の修正

コメント部分の修正を行います。
Comments Controller回りの修正なので、新規作成時と同様にログインしているユーザーのIDがDBに入るように変更します。

スクリーンショット (46).png

resources\views\bbs\show.blade.php
        @if (Route::has('login'))
            @auth
                <form class="mb-4" method="POST" action="{{ route('comment.store') }}">
                    @csrf

                    <input
                        name="post_id"
                        type="hidden"
                        value="{{ $post->id }}"
                    >

                    <!-- <div class="form-group"> // ここから
                        <label for="subject">
                        名前
                        </label>

                    <input
                            id="name"
                            name="name"
                            class="form-control {{ $errors->has('name') ? 'is-invalid' : '' }}"
                            value="{{ old('name') }}"
                            type="text"
                        >
                        @if ($errors->has('name'))
                        <div class="invalid-feedback">
                        {{ $errors->first('name') }}
                        </div>
                        @endif
                    </div> --> // ここまで

                    <div class="form-group">
                        <label for="body">
                        本文
                        </label>

                        <textarea
                            id="comment"
                            name="comment"
                            class="form-control {{ $errors->has('comment') ? 'is-invalid' : '' }}"
                            rows="4"
                        >{{ old('comment') }}</textarea>
                        @if ($errors->has('comment'))
                        <div class="invalid-feedback">
                        {{ $errors->first('comment') }}
                        </div>
                        @endif
                    </div>

                    <div class="mt-4">
                        <button type="submit" class="btn btn-primary">
                        コメントする
                        </button>
                    </div>
                </form>
            @endauth
        @endif

削除箇所(ここから ここまで)をコメントアウトしています。

app\Http\Controllers\CommentsController.php
use Illuminate\Support\Facades\Auth; // ← 追記

    public function store(CommentRequest $request)
    {
        $id = Auth::id(); // ← 追記
        $savedata = [
            'post_id' => $request->post_id,
            'user_id' => $id, // ← 'name' => $request->name, から変更
            'comment' => $request->comment,
        ];

        $comment = new Comment;
        $comment->fill($savedata)->save();

        return redirect()->route('bbs.show', [$savedata['post_id']])->with('commentstatus','コメントを投稿しました');
    }

同様に、ログインしているユーザーのidを取るためにAuthを使うので、useを追記します。
取ってきたidを\$idに入れて、\$savedataのnameだったところを修正します。

続いて、同様にバリデーションに引っ掛かるのでCommentRequestを下記のように修正します。

app\Http\Requests\CommentRequest.php
    public function rules()
    {
        return [
            // 'name' => 'required|max:40', // ← 削除
            'comment' => 'required|max:350',
        ];
    }

    /**
     * エラーメッセージを日本語化
     * 
     */
    public function messages()
    {
        return [
            // 'name.required' => '名前を入力してください', // ← 削除
            // 'name.max' => '名前は40文字以内で入力してください', // ← 削除
            'comment.required' => 'コメント本文を入力してください',
            'comment.max' => 'コメント本文は350文字以内で入力してください',
        ];
    }

以上で、アクセス制御の修正は終了です。

一応、ルーティングで気が付いた箇所があったので下記のように修正します。

routes\web.php
Route::resource('/bbs', PostsController::class)->parameter('bbs', 'post')->only([
    'index', 'show', 'create', 'store', 'edit', 'update', 'destroy'
]);

↓ 変更 ↓

Route::resource('/bbs', PostsController::class)->parameter('bbs', 'post');
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1