【Laravel講座】掲示板を作ろう!! byリク で、laravelでcrudを確認してみる。

リファレンスサイト
---
https://www.youtube.com/watch?v=-ZU48CRIWFU&list=PLTp8vyhuGlQObtOcEzsIvZn5N8P-pqnen&index=1&ab_channel=%E3%83%AA%E3%82%AF%E3%80%90Laravel%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%80%91

https://www.youtube.com/channel/UCtNhAzzZARFTHOaCjCoCylw

🔽リクさんの運営ブログ
https://rikulog.com/

[WEBOTV / WEBプログラム学習チャンネル](https://www.youtube.com/watch?v=Br_Tx619k9E&list=PL3B2bjwrmhfSg8wVfjW-ldH8zNu_xf7zB&index=1&ab_channel=WEBOTV%2FWEB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E5%AD%A6%E7%BF%92%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB)　
どっちにしようか迷った。
どちらとも、素晴らしい内容だ。

laravelを使っていれば、理解する必要もなく出来てしまう。
動画を見直していたら、全く理解していなかったことがよくわかった。

laravelのbladeコンポーネントが物凄く良かった。
btn とか　リンクとか　後々編集するのは大変、やる気すらおきん。
なぜ、btnやリンクみたいなのをコンポーネント化するのが不思議だったが、
やっと意味がわかった、始めっから、コンポーネント化して使用するべきだった。
<hr>

start project
---
機能紹介
　　ログイン機能
　　掲示板機能
　　　　記事の投稿
　　　　コメントの入力
　　検索機能
　　　　カテゴリー検索
　　　　テキスト検索
　　ハッシュタグ機能
　　　　タグの貼り付けと検索

作成するテーブル
　users
　posts
　comments
　categories
後で、
　tags
　post_tag
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/84999382-3347-ef0f-d172-01899adec7af.png)


```:　　　　　　　install　　　　　　　　
laravel new laravel_create_board_by_riku
```
データーベースの設定

```:env　DBをsqliteに変更　　
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel_create_board_by_riku2
# DB_USERNAME=root
# DB_PASSWORD=
```
```:win　database/database.sqliteの作成
type nul > database/database.sqlite
```
```:mac　database/database.sqliteの作成
touch database/database.sqlite
```
```php5:config\app.php　デバッグをtrueに変更
//false->true に変更
    'debug' => (bool) env('APP_DEBUG', true),
```
```:Laravel　Debugbarをインストール１
composer require barryvdh/laravel-debugbar --dev
```
```:Laravel　Debugbarをインストール２
php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"
```
[uiとログイン機能のインストール](https://github.com/laravel/ui)


```
composer require laravel/ui
```
```
php artisan ui bootstrap --auth
```
```
npm install && npm run dev
```
```
npm run dev
```
###日本語化のための設定 [@mitashun](https://qiita.com/mitashun/items/160784ce352cf529f853)

https://qiita.com/mitashun/items/160784ce352cf529f853

```php5:config\app.php
    'timezone' => 'Asia/Tokyo',
    'locale' => 'ja',
    'faker_locale' => 'ja_JP',
```
```
php -r "copy('https://readouble.com/laravel/5.6/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
```
```
php -f install-ja-lang.php 
```
```
php -r "unlink('install-ja-lang.php');"
```
```:resources/lang/ja.jsonファイルの作成　空のファイルに下のコードをコピペ
type nul > resources/lang/ja.json
```
```json:resources\lang\ja.json
{
    "Register": "ユーザー登録",
    "Name": "氏名",
    "E-Mail Address": "メールアドレス",
    "Password": "パスワード",
    "Confirm Password": "パスワード (確認用)",
    "Login": "ログイン",
    "Logout": "ログアウト",
    "Remember Me": "ログインしたままにする",
    "Forgot Your Password?": "パスワードを忘れた方はこちら",
    "Reset Password": "パスワード再設定",
    "Send Password Reset Link": "パスワード再設定用のリンクを送る",
    "Verify Your Email Address": "メールアドレス認証を行ってください",
    "A fresh verification link has been sent to your email address.": "ユーザー登録の確認メールを送信しました。",
    "Before proceeding, please check your email for a verification link.": "メールに記載されているリンクをクリックして、登録手続きを完了してください。",
    "If you did not receive the email,": "メールが届かない場合、",
    "click here to request another.": "こちらをクリックして再送信してください。",
    "Please confirm your password before continuing.": "続行するにはパスワードを入力してください。"
}
```
```php5:resources\lang\ja\passwords.php
-------------------省略----------------
    'user' => "メールアドレスに一致するユーザーは存在していません。",
//+
    'throttled' => 'しばらく待ってから再度お試しください。',
```
```php5:resources\lang\ja\validation.php
    'attributes' => [
//+
        "name" => "名前",
        "password" => "パスワード",
        "password_confirmation" => "パスワード(確認用)",
        "email" => "メールアドレス",
    ],
```
パスワード再発行メールの日本語化

https://blog.capilano-fw.com/?p=289

```:env　編集　アクションボタンのURLの返信先のアドレスになる。　
APP_URL=https://localhost/laravel_create_board_by_riku/public
```
パスワードリセットメールの作成

```
php artisan make:notification ResetPasswordNotification
```
パスワードリセットメール
日本語に作り変える。Notificationクラスで宛先、主題やメールのテキストを
作成できる。

```php5:app\Notifications\ResetPasswordNotification.php
class ResetPasswordNotification extends Notification
{
    use Queueable;
//+
    public $token;

    public function __construct($token)
    {
//+
        $this->token = $token;
    }

//+
    public function toMail($notifiable)
    {
//dd($notifiable)
        return (new MailMessage)
//sendPasswordResetNotification()はdefaultで'vendor.notifications.email'
//->markdown('vendor.notifications.email')指定する必要はないが、
//指定して、別のファイルに変更することもできる。区切り文字は'/'ではなく'.','blade.php'はイラン
            ->markdown('vendor.notifications.email')
//actionbtnの色を緑色に変更できる
            ->success() //->error()
            ->from('admin@example.com', config('app.name'))
            ->subject($notifiable->name.'様 パスワード再発行いたしました。')
            //actionbtnの前のラインで表示
            ->line('パスワード再発行リクエストがありましたので、メッセージ送信しました。')
//config('app.url')でenvのAPP_URLを取得,
//route('password.reset', $this->token, false))) 
//第3引数のfalseで相対パスを作成。
            ->action('パスワード再設定', url(config('app.url').route('password.reset', $this->token, false)))
            //actionbtnの後のラインで表示
            ->line('もし心当たりがない場合は、本メッセージは破棄してください。');
    }
}
```
dd($notifiable);
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c28a1874-526b-b056-631d-86950f8710f1.png)

メール本文、日本語に作り変える。２
メールのレイアウト等、より詳細に文面を作成する。
Notificationはクラスを作成して、bladeも作成する２段階設定になっている。

```
php artisan vendor:publish --tag=laravel-notifications
```
```php5:resources\views\vendor\notifications\email.blade.php
@component('mail::message')
{{-- Greeting --}}
@if (! empty($greeting))
# {{ $greeting }}
@else
@if ($level == 'error')
# Whoops!
@else
# こんにちは。
@endif
@endif

{{-- Intro Lines --}}
@foreach ($introLines as $line)
{{ $line }}

@endforeach

{{-- actionBtnの前のライン  --}}
@isset($actionText)
<?php
    switch ($level) {
        case 'success':
{{-- yellowとかしてもアカンかった。--}}
            $color = 'green';
            break;
        case 'error':
            $color = 'red';
            break;
        default:
            $color = 'blue';
    }
?>
@component('mail::button', ['url' => $actionUrl, 'color' => $color])
{{ $actionText }}
@endcomponent
@endisset

{{-- actionBtnのあとのライン  --}}
@foreach ($outroLines as $line)
{{ $line }}

@endforeach

{{-- Salutation --}}
@if (! empty($salutation))
{{ $salutation }}
@else
{{ config('app.name') }} より
@endif

{{-- Subcopy --}}
@isset($actionText)
@component('mail::subcopy')
もし、「{{ $actionText }}ボタン」がうまく機能しない場合、以下のURLをコピー＆ペーストして直接ブラウザからアクセスしてください。
 [{{ $actionUrl }}]({{ $actionUrl }})
@endcomponent
@endisset
@endcomponent
```
`@component('mail::message')`等の中を直接編集したい場合。

```
php artisan vendor:publish --tag=laravel-mail
```


```php5:app\Models\User.php
//+
use App\Notifications\ResetPasswordNotification;
-----------省略---------------

class User extends Authenticatable
{
-----------省略---------------
//+ パスワードリセットメールの変更
//sendPasswordResetNotification()はdefaultで'vendor.notifications.email'を指定
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new ResetPasswordNotification($token));
    }
}
```
[メールの設定：大阪コード学園](https://www.youtube.com/watch?v=uFEcsmIArQA&list=PLkQpCfbvj0lxihMoVzymCl-JcJbVsE0fA&index=25&ab_channel=%E5%A4%A7%E9%98%AA%E3%82%B3%E3%83%BC%E3%83%89%E5%AD%A6%E5%9C%92)

```:env
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=xxxxxxx@gmail.com
MAIL_PASSWORD=gcegjkehfdsftksf
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=xxxxxxx@gmail.com
MAIL_FROM_NAME="メッセージが届いています"
```
```
php artisan migrate
```
1.[ユーザー登録をする。](https://localhost/laravel_create_board_by_riku2/public/register)
2.validation等の日本語を確認する

```html:resources\views\auth\login.blade.php　&　register.blade.php
//novalidateを貼ってvalidationの日本語化を確認する。
   <form method="POST" action="{{ route('login') }}" novalidate>
```
3.パスワードリセットを送って、メールの日本語と
　　アクションボタンのURLが適切に機能するか確認する。

:::note info
上記1,2,3の機能を確認する。
:::

###モデル・テーブルを作成する。
1.posts　2.comments　3.categorie

```
php artisan make:model Post -a
```
```
php artisan make:model Comment -a
```
[Laravelのマイグレーション用に覚えておくべきデータ型６個](https://biz.addisteria.com/laravel_migration_data_type/)

| データ型 | 説明 | 使用時 |
|:-----------|:-----------|:-----------|
| integer       | INTEGERカラム        | 整数       |
| string    | VARCHARカラム      | 名前など短めの文字列      |
| text      | TEXTカラム        | コメントなどの文字列       |
| longText         | LONGTEXTカラム         | かなり長い文字列          |
| unsignedBigInteger       | 符号なしBIGINTカラム      | 他のテーブルのID      |
| boolean    | aligned     | true/false     |

```php5:database\migrations\xxxxx_create_posts_table.php
    public function up()
    {
        //外部キー制約を無効
        Schema::disableForeignKeyConstraints();
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            //not nullableでﾘﾚｰｼｮﾝ先のrecordが更新されたら更新、削除されたら削除
            $table->foreignId('user_id')
                   ->constrained()
                   ->cascadeOnDelete()
                   ->cascadeOnUpdate();
            $table->foreignId('category_id')
                   ->constrained()
                   ->cascadeOnDelete()
                   ->cascadeOnUpdate();
            $table->string('title');
            $table->text('content');
            $table->timestamps();

            //nullableでﾘﾚｰｼｮﾝ先のrecordが更新されたら更新、削除されたらnull値に変更
                //$table->foreignId('to_user_id')
                //      ->nullable()
                //      ->constrained("users")
                //      ->cascadeOnUpdate()
                //      ->nullOnDelete();
        });
        //外部キー制約を有効
        Schema::enableForeignKeyConstraints();
    }
```
```php5:database\migrations\xxxxx_create_comments_table.php
    public function up()
    {
        //外部キー制約を無効
        Schema::disableForeignKeyConstraints();
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')
            ->constrained()
            ->cascadeOnDelete()
            ->cascadeOnUpdate();
            $table->foreignId('post_id')
            ->constrained()
            ->cascadeOnDelete()
            ->cascadeOnUpdate();
            $table->text('content');
            $table->timestamps();
        });
        //外部キー制約を有効
        Schema::enableForeignKeyConstraints();
    }
```
```php5:app\Models\Post.php
//+ホワイトリスト方式createやupdateとfillの時に登録してある
//カラムだけが、保存できる。insert()は対象外
    protected $fillable = [
        'user_id',
        'title',
        'content',
        'category_id',
    ];
```
```php5:app\Models\Comment.php
//+
    protected $fillable = [
        'user_id',
        'post_id',
        'content',
    ];
```
```
php artisan make:model Category -a
```
```php5:database\migrations\xxxxxxx_create_categories_table.php
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }
```
```php5:app\Models\Category.php
//+
    protected $fillable = [
        'name',
    ];
```
```
php artisan migrate
```
###テーブルに、ダミーデータを追加する
users,posts,categoryテーブルにダミーデータを入力
ここは、いくらでも無駄にコレるが、
[コレ](https://qiita.com/naoqoo2/items/5cd2b96a8b2bdd60bd79)さえ、あれば、もう何でもかける。

```php5:database\seeders\DatabaseSeeder.php
//+
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

    public function run()
    {
//db:seedする度にテーブルを消去してくれるtruncate()
//truncate()する時、外部キーが邪魔するので、一旦無効にしている。
Schema::disableForeignKeyConstraints();

        DB::table('categories')->truncate();
        DB::table('users')->truncate();
        DB::table('posts')->truncate();
        DB::table('comments')->truncate();

//外部キーを有効に戻す
Schema::enableForeignKeyConstraints();
//正直、ダミーデータを入力するくらいなら、これで十分。
        DB::table('categories')->insert(
        [
            ['name'=>'book'],
            ['name'=>'travel'],
            ['name'=>'cafe'],
        ]
        );
//コメントを解除するだけ。
        $users = \App\Models\User::factory(10)->create();
//雛形にして、使いまわしている。
        $users->each(function (\App\Models\User $user) use($users) {
            \App\Models\Post::factory(3)->create(
                [
                    'user_id' => $user->id,
                ]
            )->each(function ($post) use($users){
                for($i=1;$i<=4;$i++){
                    \App\Models\Comment::factory()->create(
                        [
                            'post_id' => $post->id,
//User::where()はエラーになる。
                            'user_id' => $users->where('id','<>',$post->user_id)->random()->id,
                        ]
                    );
                }
            });
        });
    }
```
```php5:database\factories\PostFactory.php
//+
use App\Models\Category;

-----

    public function definition()
    {
        $categories = Category::all();
        return [
            'category_id' => $categories->random()->id,
            'title' =>$this->faker->realText(rand(10,20)),
            'content' =>$this->faker->realText(),
        ];
    }
```
```php5:database\factories\CommentFactory.php
    public function definition()
    {
        return [
            'content' => $this->faker->realText(rand(20,30)),
        ];
    }
```
```
php artisan db:seed
```
###ログイン後のトップページを変更する

```php5:app\Providers\RouteServiceProvider.php
//  public const HOME = '/home';
   public const HOME = '/';
```
PostのCRUDを実装する
---
ルートを作成する

```php5:routes\web.php
//コメントアウト
// Route::get('/', function () {
//     return view('welcome');
// });

Route::middleware(['auth'])->group(function () {
    Route::get('/', [App\Http\Controllers\PostController::class, 'index'])->name('posts.index');
    Route::resource('/posts', App\Http\Controllers\PostController::class,['except'=>'index']);
});

// onlyを使う方法
//Route::resource('hoge', App\Http\Controllers\PostController::class, ['only' => ['index', 'create', 'edit', 'store', 'destroy']]);
// exceptを使う方法
//Route::resource('hoge', App\Http\Controllers\PostController::class, ['except' => ['show', 'update']]);
```
https://qiita.com/sympe/items/9297f41d5f7a9d91aa11

| Verb   | Uri | Action |Route Name |
|:-----------|:-----------|:-----------|:-----------|
| GET       | /posts        | index         | posts.index |
| GET       | /posts/create | create       | posts.create |
| POST       | /posts       | store        | posts.store  |
| GET         | /posts/{post}| show         | posts.show   |
| GET       |  /posts/{post}/edit| edit     | posts.edit   |
| PUT/PATCH  |  /posts/{post}    | update   | posts.update |
| DELETE    |  /posts/{post}    | destroy    | posts.destroy|

Postの一覧画面を作成する
---
layouts.appの編集

```html:resources\views\layouts\app.blade.php
 {{-- +asset('css/app.css')の下 --}}
    @stack('css')

-----
<!-- Scripts deferを消去して</body>の上に移動-->
    <script src="{{ asset('js/app.js') }}"></script>
<!-- + -->
    @stack('js')
```

posts.indexの作成

コントローラーを編集する
posts.index(一覧画面)を作成する
`edit:`app\Http\Controllers\PostController.php

```php5:app\Http\Controllers\PostController.php
    public function index()
    {
        $posts = Post::all();
        //dd($posts);

        return view('posts.index',compact('posts'));
    }
```
postsとusersでリレーションする。

```php5:app\Models\User.php
//laravelの命名規則通りに外部キーはuser_id <-> 主キーがid のため、明示する必要がない。
//どちらかが、命名規則外だと、それぞれ、明示する必要がある。
//+ users->postsは1対多
    public function posts()//複数
    {
        return $this->hasMany(Post::class);

// return $this->hasMany(Post::class, 'user_id');//外部キーを明示
// return $this->hasMany(Post::class, 'user_id', 'id');//主キーも明示
    }
```
postsとusersとcategoriesでリレーションする。

```php5:app\Models\Post.php

//+posts->usersは多対1
    public function user() //単数
    {
        return $this->belongsTo(User::class);

// return $this->belongsTo(User::class, 'user_id');
// return $this->belongsTo(User::class, 'user_id', 'id');
    }

//+ posts->categoryは多対1
    public function category() //単数
    {
//外部キーはpostsにあって参照する側もposts 普通、参照する側に外部キーを持たすらしい
        return $this->belongsTo(Category::class);
//外部キーと主(対)キーを明示する場合
        //return $this->belongsTo(Category::class,'category_id','id');
    }

```
```php5:app\Models\Category.php
//+ category->postsは1対多
    public function posts() //複数形
    {
        return $this->hasMany(Post::class);

//外部キーと主(対)キーを明示する場合
        //return $this->hasMany(Post::class,'category_id','id');
    }
```

`create:`resources\views\posts\index.blade.php

```php5:resources\views\posts\index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">投稿一覧</div>
                    <div class="card-body">
<!-- $postが繰返えされている -->
                        @foreach ($posts as $post)
                            <div class="
                            card mt-1">
                            <div class="card-body">
                                <h5 class="card-title">{{ $post->title }}</h5>
<!-- さらに、ｶﾃｺﾞﾘｰﾃｰﾌﾞﾙとﾘﾚｰｼｮﾝしている 関係は 多対1 -->
                                <p><a href="" class="card-text">{{ $post->category->name }}</a></p>
<!--{!! nl2br(e($post->content)) !!} 改行+サニタイズ -->
                                <p class="card-text mt-1">{!! nl2br(e($post->content)) !!}</p>
<!--リンク先は route('ﾙｰﾄﾈｰﾑ',$model) で取得できる -->
                                <a href="{{ route('posts.show', $post) }}" class="btn btn-primary">コメ</a>
                            </div>
                        </div>
                        @endforeach
                </div>
            </div>
        </div>
    </div>
    </div>
@endsection
```
◎ｶﾃｺﾞﾘｰﾃｰﾌﾞﾙとﾘﾚｰｼｮﾝしている。{{ $post->category->name }}
　　関係は 多対1　[N+1問題](https://www.google.com/search?q=n%E5%AF%BE1%E5%95%8F%E9%A1%8C&sxsrf=AOaemvJ9qoGpduwsd70FSDaI8mC6ZcskVQ%3A1632557150857&ei=XthOYajsM8qhhwOC_abYDg&oq=n%E5%AF%BE1%E5%95%8F%E9%A1%8C&gs_lcp=Cgxnd3Mtd2l6LXNlcnAQAzIFCAAQzQI6DggAEIAEELADEAQQJRAgSgQIQRgBUMAfWKwpYPw0aANwAHgAgAF6iAHpBZIBAzYuMpgBAKABAcgBAcABAQ&sclient=gws-wiz-serp&ved=0ahUKEwiok7Wo1ZnzAhXK0GEKHYK-CesQ4dUDCA4&uact=5)が生じている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/137308e1-9c1d-3210-891f-0e1948a942d1.png)
N+1問題を解決するために、先にデータを全部取得しておく。

```php5:app\Http\Controllers\PostController.php
    public function index(Request $request)
    {

        $posts = Post::with('category')->get();
        // dd($posts);
        return view('posts.index',compact('posts'));
    }
```
それぞれのポストItemsにカテゴリーのリレーションが貼られる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/79eb0faa-2224-ca40-5ac2-bda32ba9202b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/d5a1b13c-1dd5-b9e0-5c03-f8033007d140.png)
クエリの発行数が31回->2回に激減した。
リレーション関係が多対1で多のモデルが1を繰り返し取得するため、N+1問題が生じている。そのため、EagerLoadingで一括して多に1の情報を貼り付けて取得している。

投稿者の名前を追記する。posts->usersテーブル(多対1)

```php5:resources\views\posts\index.blade.php
                                    <h5 class="card-title">{{ $post->title }}</h5>

<!-- ｶﾃｺﾞﾘｰﾈｰﾑの上に追記 ポスト->ユーザーの関係は多対1 -->
                                    <p><a href="" class="card-text">{{ $post->user->name }}</a></p>
                                    <!-- 記入済み category->name -->
                                    <p><a href="" class="card-text">{{ $post->category->name }}</a></p>
```
また、多のモデルが繰り返されクエリーが発行されている
N+1問題が生じているので、同じように、EagerLoadingが必要
N+1問題が生じるか生じないかは単純な規則がある。
<font color="red">1</font>対1、<font color="red">1</font>対多、<font color="red">多</font>対1、<font color="red">多</font>対多
赤のモデルが繰り返されるとN+1問題が発生する。
ここでは、{{<font color="red">$post</font>->user->name }}で発生する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/6c4037c7-3dc4-8dec-265e-56262fa1899b.png)


```php5:app\Http\Controllers\PostController.php
//変更リレーションuserにもEagerLoadingする。
    public function index()
    {
//複数のテーブルをwithやloadでリレーションする時は配列にしてやるだけ。
        $posts = Post::with(['category','user'])->get();
        // dd($posts);
        return view('posts.index', compact('posts'));
    }
```
見違えるようにスッキリした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/b43e2c06-edbf-a61e-1b7c-40c7a51fe532.png)
###ページネーションを実装する。
bootstrap4のージネーションを実装する場合、
app\Providers\AppServiceProvider.phpで明示する必要がある。

```php5:app\Providers\AppServiceProvider.php
//+
use Illuminate\Pagination\Paginator;
----

    public function register()
    {
//+
        Paginator::useBootstrap();
    }
```
```php5:app\Http\Controllers\PostController.php
//変更
    public function index()
    {
        // $posts = Post::with(['category','user'])->get();
//get->paginate(数)に変更
        $posts = Post::with(['category','user'])->paginate(3);

        // dd($posts);
        return view('posts.index',compact('posts'));
    }
```
```php5:resources\views\posts\index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">投稿一覧</div>
                    <div class="card-body overflow-auto">
                        @foreach ($posts as $post)
                            <div class="
                            card mt-1">
                            <div class="card-body">
                                <h5 class="card-title">{{ $post->title }}</h5>
                                <a href="" class="card-text">{{ $post->category->name }}</a>
                                <p class="card-text mt-1">{!! nl2br(e($post->content)) !!}</p>
                                <a href="{{ route('posts.show', $post) }}" class="btn btn-primary">詳細 コメントを投稿</a>
                            </div>
                        </div>
                        @endforeach
                </div>
<!-- + -->
                {{ $posts -> links() }}
            </div>
        </div>
    </div>
    </div>
@endsection
```
モデルへのアクセス数が33回から5回へと激減している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/a28029e6-77a8-baec-280c-39bac6ad58a1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/505cbc09-7c5c-e997-3994-aecb1088d5df.png)
ページネーションをカスタマイズする。
今回は中央寄せをページネーションの方で書き換える。

```
php artisan vendor:publish --tag=laravel-pagination
```
resources\views\vendor\pagination にページネーションのファイルが作成される。
不要なやつは削除する。

```html:resources\views\vendor\pagination\bootstrap-4.blade.php
@if ($paginator->hasPages())
{{-- nav要素にmx-autoを追記 --}}
    <nav class="mx-auto mt-2">
```
中央寄せ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/b3445908-7257-f49b-43f8-91b8b51f8f6e.png)

<details><summary>シンプルページネーションに変更してみる。</summary><div>

```php5:resources\views\posts\index.blade.php
                       @endforeach
                </div>
<!-- 修正 linksに明示 view.name(viewsからの相対パスで記述) -->
           {{ $posts -> links('vendor.pagination.simple-bootstrap-4') }}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/843f443c-1da6-768a-41ca-c8c2c31cb735.png)

```php5:app\Providers\AppServiceProvider.php
    public function register()
    {
//明示した場合、コメントアウトできる。
    //   Paginator::useBootstrap();
    }
```
</div></details>
ポストの詳細画面を表示する
---
コメントを投稿ボタンをリンクにして、詳細画面を表示させる。

```php5:app\Http\Controllers\PostController.php
//+$postをviewにリターンしてやるだけ。
    public function show(Post $post)
    {
        //dd($post)
        return view('posts.show',compact('post'));
    }
```
リレーションは無いが`$post`の時はN+1問題は生じない
　　　　　　　　　　　　　　　　のでEagerLoadingする必要がない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/82294049-9028-b230-5e79-a2da47a43494.png)
これが、`$posts`だったら、繰り返しが生じた場合、N+1問題が生じる。



`create:`resources\views\posts\show.blade.php

```php5:resources\views\posts\show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ $post->title }}</div>
                <div class="card-body">

                    <p>カテゴリー：<a href="">{{ $post->category->name }}</a></p>
                    <p>{!! nl2br(e($post->content)) !!}</p>

                </div>
                <div class="card-footer">
                    <p>投稿時間：{{ $post->created_at  }}</p>
                    <a href="{{ route('posts.index') }}">戻る</a>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/0e549d3a-a3d3-6c72-3404-a0bf825bb396.png)
投稿画面を作成する
---
posts.createの作成
これはshow画面をformに作り変えてやればいいだけ。
index画面にposts.createのリンクを貼ってやる。

```php5:resources\views\posts\index.blade.php　ｶｰﾄﾞﾍｯﾀﾞｰを修正
            <div class="col-md-8">
                <div class="card">
{{-- - ｶｰﾄﾞﾍｯﾀﾞｰを修正 --}}
                    {{-- <div class="card-header">投稿一覧</div> --}}
{{-- + --}}
                    <div class="card-header d-flex justify-content-between">
                        <span>投稿一覧</span>
                        <a href="{{ route('posts.create') }}">投稿</a>
                    </div>
```

```php5:app\Http\Controllers\PostController.php
//+
use App\Models\Category;
//+
    public function create()
    {
        $categories = Category::all();
        return view('posts.create', compact('categories'));
    }
```
`create:`resources\views\posts\create.blade.php

```php5:resources\views\posts\create.blade.phplogin
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">投稿</div>
                <div class="card-body">
                    <form method="POST" action="{{ route('posts.store') }}" novalidate>
                        @csrf

                        <div class="form-group row no-gutters">
                            <label for="title" class="col-md-4 col-form-label text-md-left">タイトル</label>

                            <div class="col-md-8">
                                <input id="title" type="text" class="form-control @error('title') is-invalid @enderror" name="title" value="{{ old('title') }}" required autocomplete="title" autofocus>

                                @error('title')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row no-gutters">
                            <label for="category" class="col-md-4 col-form-label text-md-left">カテゴリー</label>

                            <div class="col-md-8">
                                <select name="category_id" id="" class="form-control @error('category_id') is-invalid @enderror" >
                                    <option value="">未選択</option>
                                    @foreach ($categories as $category)
                                    <option value="{{ $category->id }}" @if (old('category_id') == $category->id)
                                        selected
                                    @endif>{{ $category->name }}</option>
                                    @endforeach
                                </select>

                                @error('category_id')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row no-gutters">
                            <label for="content" class="col-md-4 col-form-label text-md-left">コンテンツ</label>

                            <div class="col-md-8">
                                <textarea name="content" id="" cols="30" rows="10" class="form-control @error('content') is-invalid @enderror" >{{ old('content') }}
                                </textarea>
                                @error('content')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row mb-0 no-gutters">
                            <div class="col-md-8 offset-md-4 text-right">
                                <button type="submit" class="btn btn-primary">
                                    投稿
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="card-footer text-right">
                    <a href="{{ route('posts.index') }}">戻る</a>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
###バリデーションクラスを作成する
```
php artisan make:request PostRequest
```
app\Http\RequestsにPostRequest.phpが作成される。

```php5:app\Http\Requests\PostRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;

//+
use App\Models\Category;

class PostRequest extends FormRequest
{
    public function authorize()
    {
        //falseからtureに変更 認証系の機能をココで作成できる。
        //falseだと403エラーになる。
        //$this->nameで$_REQUEST['name']を取得できる。
        return true;
    }
    public  function  rules()
    {
        $categories  =  Category::get('id')->implode('id', ',');

        return  [
            'title'  =>  ['required',  'min:10', 'max:25'],
            'content'  =>  'required',
            'category_id'  =>  "required|in:$categories",
        ];
    }
//日本語化済み。
    public  function  messages()
    {
        return  [
            // "required"  =>  ":attributeは必須項目です。",
            // "email"  =>  ":attributeはメールアドレスの形式で入力してください。",
            // "numeric"  =>  ":attributeは数値で入力してください。",
            // "opinion.max"  =>  ":attributeは500文字以内で入力してください。"
        ];
    }
    public  function  attributes()
    {
        return  [
            'title'  =>  'タイトル',
            'content'  =>  '内容',
            'category_id'  =>  'カテゴリー',
        ];
    }
}
```
```php5:
//+
use App\Http\Requests\PostRequest;

//Request から PostRequest  に変更するだけ。
    public function store(PostRequest $request)
    {
        //
    }
```

:::note info
formにnovalidateをつけて、バリデーションを確認する。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c14e0a85-0cca-2c12-a3da-a029222f1144.png)
投稿をデーターベースに保存する。
---
posts.storeの作成

```php5:app\Http\Controllers\PostController.php
use Illuminate\Support\Facades\Auth;

    public function store(PostRequest $request)
    {
        $request->merge(['user_id'=>Auth::id()]);
//$fillableしているからもう、これで十分だと思う。
        Post::create($request->all());
//flashメッセージをつけて、index画面にリダイレクト
return redirect()->route('posts.index')->with('status', ['success' => '投稿完了']);
    }
```
posts.indexを修正するflashメッセージを表示させる。
コンポーネントを使用してflashメッセージ表示してみる。

```php5:resources\views\posts\index.blade.php
@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">

<!-- コンポーネントファイルの読み込み -->
                <x-flashMessage/>

                <div class="card">
```
`resources\views\components\`　の直下に `*.blade.php`を作成するだけ。
読み込みは `<x-*/>`で読み込める。

```php5:resources\views\components\flashMessage.blade.php
@if (session()->has('status'))
@foreach (session('status') as $key => $session)
<div class="alert alert-{{$key}} alert-dismissible fade show" role="alert">
    <strong>{{ $session }}</strong>
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
@endforeach
@endif
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/3c502722-e1db-13be-ab98-5fd8bb1c4117.png)
###画像投稿機能を追加する
posts.create画面を修正

```php5:resources\views\posts\create.blade.php
<!-- 追記 enctype="multipart/form-data" -->
    <form method="POST" action="{{ route('posts.store') }}" novalidate enctype="multipart/form-data">

<!-- + <button>の直上 -->
    <div class="form-group row no-gutters">
        <label for="content" class="col-md-4 col-form-label text-md-left">画像</label>
        <div class="col-md-8">

            <x-fileRender></x-fileRender>

        </div>
    </div>
<!-- + ここまで -->
```
https://v4.bootstrap-guide.com/javascript/forms/file-browser
コンポーネント化することで、使いまわしたり、切って可読性を向上できる。
`create:`resources\views\components\fileRender.blade.ph

```php5:resources\views\components\fileRender.blade.php
{{-- https://v4.bootstrap-guide.com/javascript/forms/file-browser --}}
@push('css')
    <style>
        .custom-file {
            max-width: 20rem;
            overflow: hidden;
        }

        .custom-file-label {
            white-space: nowrap;
        }

    </style>
@endpush

<div class="form-group">
    <label for="file">ファイル（4つまで選択可）</label>
    <div id="file" class="input-group">
        <div class="custom-file">
            <input type="file" id="cutomfile" class="custom-file-input" name="cutomfile[]" multiple  accept="image/*" />
            <label class="custom-file-label" for="customfile" data-browse="参照">ファイル選択...</label>
        </div>
        <div class="input-group-append">
            <button type="button" class="btn btn-outline-secondary reset">取消</button>
        </div>
    </div>
    @if ($errors->has('cutomfile.*'))
        @foreach ($errors->get('cutomfile.*') as $message)
            <span class="text-danger d-block m-0"> {{ $message[0] }} </span>
        @endforeach
    @endif
</div>

@push('js')
    <script>
        function check(file) {
            if (file.length > 4) {
                $('.custom-file-input').val('');
            }
        }
        $('.custom-file-input').on('change', handleFileSelect);

        function handleFileSelect(evt) {

            $('#preview').remove(); // 繰り返し実行時の処理
            $(this).parents('.input-group').after('<div id="preview"></div>');

            var files = evt.target.files;
            check(files);
            for (var i = 0, f; f = files[i]; i++) {

                if (i > 3) {
                    return false
                }

                var reader = new FileReader();

                reader.onload = (function(theFile) {
                    return function(e) {
                        if (theFile.type.match('image.*')) {
                            // 画像では画像のプレビューとファイル名の表示
                            var $html = [
                                '<div class="d-inline-block mr-1 mt-1"><img class="img-thumbnail" src="', e
                                .target.result, '" title="', escape(theFile.name),
                                '" style="height:100px;" /><div class="small text-muted text-center">',
                                escape(theFile.name), '</div></div>'
                            ].join('');
                        } else {
                            //画像以外はファイル名のみの表示
                            // var $html = ['<div class="d-inline-block mr-1"><span class="small">', escape(theFile
                            //     .name), '</span></div>'].join('');
                        }

                        $('#preview').append($html);
                    };
                })(f);
                reader.readAsDataURL(f);
            }
            $(this).next('.custom-file-label').html(+files.length + '個のファイルを選択しました');
        }
        //ファイルの取消
        $('.reset').click(function() {
            $(this).parent().prev().children('.custom-file-label').html('ファイル選択...');
            $('#preview').remove();
            $('.custom-file-input').val('');
        })
    </script>
@endpush
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/850d4388-ff84-dd39-4c0d-4bef512bfb8d.png)
バリデーションを追加する

```php5:app\Http\Requests\PostRequest.php
    public  function  rules()
    {
        $categories  =  Category::get('id')->implode('id', ',');

        return  [
            'title'  =>  ['required',  'min:10', 'max:25'],
            'content'  =>  'required',
            'category_id'  =>  "required|in:$categories",
//+
            'cutomfile.*' => [
                'file',
                'image',
                'mimes:jpeg,png,jpg',
                'max:1024',//1MB
            ],
        ];
    }

    public  function  attributes()
    {
        return  [
            'title'  =>  'タイトル',
            'content'  =>  '内容',
            'category_id'  =>  'カテゴリー',
//+
            'cutomfile.*' => 'ファイル'
        ];
    }
```
画像を保存する。

```php5:
//+
use Illuminate\Support\Str;

    public function store(PostRequest $request)
    {
//+cutomfileがあったら、
        if ($request->has('cutomfile')) {
//ユニークなフォルダパスを作成
            $img_path = uniqid("tmp_img_") . "/";


            foreach ($request->file('cutomfile') as $key => $img_file) {
//ファイルが存在しているかに付け加え、isValidメソッドで問題なくアップロードできたのかを確認できたなら
                if ($img_file->isValid()) {
//画像なら//既にバリデーション済みだからイランが、ファイルと混合している時とか。
                    if (Str::is('image*', $img_file->getMimeType())) {
//pathを作成(path/+ ファイル名(空白は '_' に置き換えている))
                        $img_files_path = $img_path . Str::of($img_file->getClientOriginalName())->replace(' ', '_');
 // storage\appからの相対パス 画像を保存している。//取得はsrc="{{ asset('storage/'.$tmp_file)
                        $img_file->storeAs("public", $img_files_path);
                        $img_files_path = '';
                    }
                }
            }
        }

        $request->merge(
            ['user_id' => Auth::id()]
        );
//修正
        Post::create($request->except('cutomfile'));
        return redirect()->route('posts.index')->with('status', ['success' => '投稿完了']);
    }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/0158cfde-2660-dfc8-b9aa-88a1d7854bfb.png)


画像のパスを保存するカラムを追加する。

```
php artisan make:migration add_column_to_posts
```
```php5:database\migrations\_add_column_to_posts.php
class AddColumnToPosts extends Migration
{
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->string('img_path')->nullable();
        });
    }

    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('img_path');
        });
    }
}
```
```php5:app\Models\Post.php
    protected $fillable = [
        'user_id',
        'title',
        'content',
        'category_id',
//+
        'img_path',
    ];
```
```
php artisan migrate
```
コントローラーを修正する

```php5:app\Http\Controllers\PostController.php
    public function store(PostRequest $request)
    {
        if ($request->has('cutomfile')) {
            $img_path = uniqid("tmp_img_") . "/";

//$requestにimg_pathを追加
            $request->merge(
                ['img_path' => $img_path],
            );

            foreach ($request->file('cutomfile') as $key => $img_file) {
                //ファイルが存在しているかに付け加え、isValidメソッドで問題なくアップロードできたのかを確認できたなら
                if ($img_file->isValid()) {
                    //画像なら
                    if (Str::is('image*', $img_file->getMimeType())) {
                        //pathを作成(path/+ ファイル名(空白は '_' に置き換えている))
                        $img_files_path = $img_path . Str::of($img_file->getClientOriginalName())->replace(' ', '_');
                        // storage\appからの相対パス 画像を保存している。//取得はsrc="{{ asset('storage/'.$tmp_file)
                        $img_file->storeAs("public", $img_files_path);
                        $img_files_path = '';
                    }
                }
            }
        }

        $request->merge(
            ['user_id' => Auth::id()],
        );
//修正2
        Post::create($request->except('cutomfile'));
        return redirect()->route('posts.index')->with('status', ['success' => '投稿完了']);
    }
```
###画像を表示する。
リンクを作成する。

```
php artisan storage:link
```
```html:resources\views\posts\show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ $post->title }}</div>
                <div class="card-body">
                    <p>カテゴリー：<a href="">{{ $post->category->name }}</a></p>
                    <p>{{ $post->content }}</p>

{{-- +  :post="$post" でｺﾝﾎﾟｰﾈﾝﾄに値を渡している :img_path= これはエラーになる  --}}
                    <x-img_views :imgPath="$post->img_path"></x-img_views>

                </div>
                <div class="card-footer">
                    <p>投稿時間：{{ $post->created_at  }}</p>
                    <a href="{{ route('posts.index') }}">戻る</a>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```
```php5:resources\views\components\img_views.blade.php
@php
if ($imgPath) {
    $path = '/public/' . $imgPath;
    $files = Storage::files($path);
    //dd($files);
}else{
    return;
}
@endphp


@isset($files)
    @foreach ($files as $img_file)
        <div class="d-inline-block mr-1 mt-1"><img class="img-thumbnail" src="{{ asset(Storage::url($img_file)) }}"
                alt="{{ class_basename($img_file) }}" style="height:100px;" />
            <div class="small text-muted text-center">{{ class_basename($img_file) }}</div>
        </div>
    @endforeach
@endisset
```
画像を投稿して、詳細画面で確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/def65f56-89e9-b707-6d08-87418702aecc.png)
投稿を編集する
---
posts.editの作成
create画面に$postを付け加えるだけ。
リンクをshow.blade.phpにつける。

```php5:resources\views\posts\show.blade.php
{{--card-foote--}}
                <div class="card-footer">
                    <p>投稿時間：{{ $post->created_at  }}</p>
                    <a href="{{ route('posts.index') }}">戻る</a>
{{-- + --}}
                    <a class="btn btn-danger" href="{{ route('posts.edit',$post) }}">編集</a>
                </div>
```
```php5:app\Http\Controllers\PostController.php
//+
    public function edit(Post $post)
    {
        $categories = Category::all();
        return view('posts.edit', compact('categories','post'));
    }
```
個々の画像を追加したり、変更したり、削除すること考えたら、imgsテーブルを作って
img_pathを保存したほうが、良かった。反省だ。

```php5:resources\views\posts\edit.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">投稿</div>
                    <div class="card-body">
                        <!-- enctype="multipart/form-data" -->
                        <form method="POST" action="{{ route('posts.update',$post) }}" novalidate enctype="multipart/form-data">
                            @method('put')
                            @csrf

                            <div class="form-group row no-gutters">
                                <label for="title" class="col-md-4 col-form-label text-md-left">タイトル</label>

                                <div class="col-md-8">
<!-- /* {{ old('title', $post->title) }}テーブルの値はold()の第2引数に入れてやればいいだけ*/ -->
                                    <input id="title" type="text" class="form-control @error('title') is-invalid @enderror"
                                        name="title" value="{{ old('title', $post->title) }}" required autocomplete="title"
                                        autofocus>
                                    @error('title')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row no-gutters">
                                <label for="category" class="col-md-4 col-form-label text-md-left">カテゴリー</label>

                                <div class="col-md-8">
                                    <select name="category_id" id=""
                                        class="form-control @error('category_id') is-invalid @enderror">
                                        <option value="">未選択</option>
                                        @foreach ($categories as $category)
                                            <option value="{{ $category->id }}" @if (old('category_id', $post->category_id) == $category->id)
                                                selected
                                        @endif>{{ $category->name }}</option>
                                        @endforeach
                                    </select>

                                    @error('category_id')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row no-gutters">
                                <label for="content" class="col-md-4 col-form-label text-md-left">コンテンツ</label>

                                <div class="col-md-8">
                                    <textarea name="content" id="" cols="30" rows="10"
                                        class="form-control @error('content') is-invalid @enderror">{{ old('content', $post->content) }}
                                                </textarea>
                                    @error('content')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>
                            <div class="form-group row no-gutters">
                                <label for="content" class="col-md-4 col-form-label text-md-left">画像削除</label>
                                <div class="col-md-8">

                                    <x-img_views type="edit" :imgPath="$post->img_path"></x-img_views>

                                </div>
                            </div>

                            <div class="form-group row no-gutters">
                                <label for="content" class="col-md-4 col-form-label text-md-left">画像追加</label>
                                <div class="col-md-8">

                                    <x-fileRender></x-fileRender>

                                </div>
                            </div>

                            <div class="form-group row mb-0 no-gutters">
                                <div class="col-md-8 offset-md-4 text-right">
                                    <button type="submit" class="btn btn-primary">
                                        投稿
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                    <div class="card-footer text-right">
                        <a href="{{ route('posts.index') }}">戻る</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
components\img_views.blade.phpを編集する

```php5:resources\views\components\img_views.blade.php
@php
if ($imgPath) {
    $path = '/public/' . $imgPath;
    $files = Storage::files($path);
    //dd($files);
}else{
    return;
}
@endphp

{{-- 編集画面 --}}
@if (isset($type) == 'edit')
    @isset($files)
        {{-- checkboxを追加 --}}
        @foreach ($files as $img_file)
            <div class="d-inline-block mr-1 mt-1 border text-center"><label class=""><span class=" d-block mx-auto my-1
                    small"><input type="checkbox" class="align-middle" name="delete_customfile[]"
                        value="{{ $img_file }}">
                    削除</span><img class="img-thumbnail" src="{{ asset(Storage::url($img_file)) }}"
                        alt="{{ class_basename($img_file) }}" style="height:100px;" /></label>
                <div class="small text-muted text-center">{{ class_basename($img_file) }}</div>
            </div>
        @endforeach
    @endisset

@else
    {{-- そうでない画面 --}}
    @foreach ($files as $img_file)
        <div class="d-inline-block mr-1 mt-1"><img class="img-thumbnail" src="{{ asset(Storage::url($img_file)) }}"
                alt="{{ class_basename($img_file) }}" style="height:100px;" />
            <div class="small text-muted text-center">{{ class_basename($img_file) }}</div>
        </div>
    @endforeach

@endif
```
post.update()もう殆ど、posts.create()と同じ、変更点は3箇所
こういうのは、スコープを使って関数化するべきだった。反省。

```php5:app\Http\Controllers\PostController.php
//+
use Illuminate\Support\Facades\Storage;
-----------------

    public function update(PostRequest $request, Post $post)
    {
//+編集で画像を削除した場合、
        if (isset($request->delete_customfile)) {
            foreach ($request->delete_customfile as $delete_customfile) {
                $path = $delete_customfile;
                Storage::delete($path);
            }
        }

        if ($request->has('cutomfile')) {
//+
            if (is_null($post->img_path)) {
                $img_path = uniqid("tmp_img_") . "/";
                $request->merge(
                    ['img_path' => $img_path],
                );
            } else {
                $img_path = $post->img_path;
            }

            foreach ($request->file('cutomfile') as $key => $img_file) {
                if ($img_file->isValid()) {
                   
                    if (Str::is('image*', $img_file->getMimeType())) {
                        $img_files_path = $img_path . Str::of($img_file->getClientOriginalName())->replace(' ', '_');
                        $img_file->storeAs("public", $img_files_path);
                        $img_files_path = '';
                    }
                }
            }
        }
        
        $request->merge(
            ['user_id' => Auth::id()],
        );
//+作成したデータが欲しかったらupdateOrCreate()を使う。
        $post->update($request->except('cutomfile', '_token'));

        return redirect()->route('posts.index')->with('status', ['success' => '編集完了']);
    }

```

投稿を削除する機能を実装する
---
posts.destroyの作成

```php5:app\Http\Controllers\PostController.php
    public function destroy(Post $post)
    {
        if (isset($post->img_path)) {
 //削除ファイルの相対パス:storage\app\public\img\tmp\tmp_6125eaa96f4ffForest.jpg
 //基本：storage\app\以下を取得して
 // $path='public\img\tmp\tmp_6125eaa96f4ffForest.jpg';
 // Storage::delete($path); //ファイルの場合
 // Storage::deleteDirectory($path); //ディレクトリの場合


//dd($post->img_path); //= "tmp_img_615563e7bb427/"
//   storage\app\public\tmp_img_61556798e1d93
            $path ='public/'.$post->img_path;
            Storage::deleteDirectory($path);
        }
        $post->delete();
        return redirect()->route('posts.index')->with('status', ['success' => $post->id . '番を削除しました']);
    }
```
posts.index画面に削除リンクをはる

```php5:resources\views\posts\index.blade.php
{{-- -詳細をコメントアウト --}}
                                    {{-- <a href="{{ route('posts.show', $post) }}" class="btn btn-primary">詳細 コメントを投稿</a> --}}
 <!-- +1 ここ d-flexで詳細ボタンと並べる -->
                                    <div class="d-flex justify-content-between">
                                        <a href="{{ route('posts.show', $post) }}" class="btn btn-primary">詳細 コメントを投稿</a>
 <!-- posts.destroyはmethod('delete')で送る必要があるため、formを使う必要がる。 -->
 <!-- layout.appのlogoutのリンクをコピペして利用できる。 -->
 <!-- this.onclick=null;で1回だけ実行できる。 -->
<!-- javascript:void(0)は今では使っては行けないコードみたいです -->
<!-- 代わりに、href="#" or buttonを使うみたいです。 -->
                                        <a class="btn btn-danger " href="javascript:void(0)"
                                            onclick="deletePost({{ $post->id }}); this.onclick=null;">
                                            削除
                                        </a>
                                    </div>
 <!-- ここまで -->

                                </div>
                            </div>
                        @endforeach

<!-- +2 formは繰り返しをさけるため endforeach のあとに置く -->
                        <form action="" method="POST" id="delete_form" class="d-none">
                            @csrf
                            @method('delete')
                            </form>
                        
-----------------------------------------
@endsection
<!-- +3 一番下 -->
@push('js')
    <script>
        function deletePost(post_id) {
            url = `{{ route('posts.destroy','') }}/${post_id}`;
            $('#delete_form').attr('action',url).submit();
        }
    </script>
@endpush
```
ボタンを横並びに配置した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/4bb901b6-14b0-bc37-cb3f-b3e080a82a6d.png)

:::note info
画像フォルダも削除できているか、確認する。
:::

```:削除しすぎたら、db:seedでもとに戻せる。
php artisan db:seed
```
アクセス制限機能を追加する。
---
**編集**、**削除**は投稿者だけに許可するようにアクセス制限を設ける。

https://reffect.co.jp/laravel/laravel-gate-policy-understand

laravelの主なアクセス制限機能３つ
1.手動で実装する場合
2.Gate(ゲート)を利用したアクセス制限
3.Policy(ポリシー)を用いたアクセス制限

###まず１番の、手動で実装する場合
流れ
　1. 権限のない場合、フロント側でリンクを非表示にしてやる。
　2. 権限のない場合、サーバー側で処理しない。

フロント側で非表示にする。

```php5:@if　($post->user_id　==　Auth：：id())　@endif　で囲めばいいだけ。
resources\views\posts\index.blade.php
        @if ($post->user_id == Auth::id())
            <a class="btn btn-danger " href="javascript:void(0)"
                onclick="deletePost({{ $post->id }}); this.onclick=null;">
                削除
            </a>
        @endif

resources\views\posts\show.blade.php

        @if ($post->user_id == Auth::id())
            <a class="btn btn-danger" href="{{ route('posts.edit', $post) }}">編集</a>
        @endif
```
権限がないとリンクが非表示になった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/acf15933-d3e2-a526-de60-8cdea0149f7f.png)

カスタムif文に変えてみる。
<font color="red">**App**</font>ServiceProvider.phpのboot()の中に定義してやるだけ。
注意：<font color="red">**Auth**</font>ServiceProvider ではない。

```php5:app\Providers\AppServiceProvider.php
//+
use Illuminate\Support\Facades\Blade;

    public function boot()
    {
//+
        Blade::if('isPost', function ($post) {
            return (auth()->check() && auth()->id() == $post->user_id);
        });

    }
```
```php5:view
resources\views\posts\index.blade.php
        @isPost($post)
            <a class="btn btn-danger " href="javascript:void(0)"
                onclick="deletePost({{ $post->id }}); this.onclick=null;">
                削除
            </a>
            {{-- @else --}}
        @endisPost
```

サーバー側の処理は認証が拒否なら処理を拒否する。
`if(Auth::user()->id !== $post->user_id){ return }`を追加するだけ。

```php5:app\Http\Controllers\PostController.php
    public function update(PostRequest $request, Post $post)
    {
//+
        if(Auth::user()->id !== $post->user_id){
            return redirect()->route('posts.index')->with('status', ['danger' => '編集権限がありません']);
        }
--------------------------------
    }

    public function destroy(Post $post)
    {
//+
        if(Auth::user()->id !== $post->user_id){
            return redirect()->route('posts.index')->with('status', ['danger' => '削除権限がありません']);
        }

        $post->delete();
        return redirect()->route('posts.index')->with('status',['success'=>$post->id.'番を削除しました']);
    }
```
非表示を解除して実行してみた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/fe7703dc-bd21-3135-59ec-c454138c3ba4.png)
###2番の、Gate(ゲート)を利用したアクセス制限
**Gate**は**AuthServiceProvider.php**で定義

```php5:コピペ✗　app\Providers\AuthServiceProvider.php　GATEの雛形　
<?php
namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{

    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     *  GateはAuthServiceProvider.phpで定義を記述します。
     *  定義を行う時はbootメソッドの中にGateファサードのdefineメソッドを使って行う。
     */
    public function boot()
    {
        $this->registerPolicies();

//第一引数にはアクセス制限を行う際に利用する任意の名前、第二引数にはクロージャ―を設定します。
//クロージャ―の処理ではtrueかfalseかを戻します.
//クロージャーの第一引数には必ずユーザーが返される。第二引数に設定した引数がわたる
//定義：
Gate::define('isAdmin',  function ($user,  $post) {
    return  $user->id  ==  $post->user_id;
});

//使い方：
//コントローラー側
//Gate::authorize('isAdmin',$post); true or false
//authorizeメソッド、allowsメソッド、deniesメソッドを利用できる。
//blade側
//@can('isAdmin')
//@can('isAdmin',$post)
//投稿一覧を表示するhtmlを記述
//@else
//<p>貴方には、権限がありません。管理者のみが表示されます。</p>
//@endcan
    }
}
```
雛形をみて、実装する。

```php5:app\Providers\AuthServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{

    protected $policies = [
        // 'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();

//+
        Gate::define('isPost',  function ($user,  $post) {
            return  $user->id  ==  $post->user_id;
        });
    }
}
```
まずは、先程と一緒で、表示制限を実装する。

```php5:views
resources\views\posts\index.blade.php
        @can('isPost', $post)
            <a class="btn btn-danger " href="javascript:void(0)"
                onclick="deletePost({{ $post->id }}); this.onclick=null;">
                削除
            </a>
            {{-- @else --}}
        @endcan

resources\views\posts\show.blade.php
        @can('isPost', $post)
            <a class="btn btn-danger" href="{{ route('posts.edit', $post) }}">編集</a>

            {{-- @else --}}
        @endcan
```
前後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/be7bfb69-994c-efcd-d755-2a4a5019b6c8.png)
サーバ側で処理を禁止する。
authorizeメソッド、allowsメソッド、deniesメソッドによって、
falseのときの処理がことなる。

```php5:app\Http\Controllers\PostController.php
use Illuminate\Support\Facades\Gate;/*FacadesのGate*/
// use Illuminate\Auth\Access\Gate;//こっちではない。

    public function destroy(Post $post)
    {
// authorizeメソッドを使用した場合は、
//false時、403のAuthorizationExceptionがthrowされます。
        Gate::authorize('isPost', $post);

// allowsの場合は、if分により分岐処理を行う
        // if (Gate::allows('isPost', $post)) {
        //     $post->delete();
        //     return redirect()->route('posts.index')->with('status', ['success' => $post->id . '番を削除しました']);
        // } else {
        //     return redirect()->route('posts.index')->with('status', ['danger' => '編集権限がありません']);
        // }

//deniesはallowsと逆の処理を行うことができます
        // if (Gate::denies('isPost', $post)) {
        //     return redirect()->route('posts.index')->with('status', ['danger' => '編集権限がありません']);
        // } else {
        //     $post->delete();
        //     return redirect()->route('posts.index')->with('status', ['success' => $post->id . '番を削除しました']);
        // }
    }
```
authorizeメソッドの時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/5fdfce6c-6ebc-f043-a4cb-83fcba0ab9b1.png)
allowsやdeniesの場合、falseを条件文でコントロール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/758c6006-aeff-13f2-36d8-1d557a69847b.png)



<details><summary>その他のGateの機能</summary><div>
###forUserメソッドの使用方法
アクセス制限を行うために他のユーザのアクセス権を利用する場合には
forUserメソッドを使用することができます。

```php5:app\Http\Controllers\PostController.php
    public function destroy(Post $post)
    {
 // authorizeメソッドを使用した場合は、
//false時、403のAuthorizationExceptionがthrowされます。
        // Gate::authorize('isPost', $post);

// allowsの場合は、if分により分岐処理を行う
        // if (Gate::allows('isPost', $post)) {
        //     $post->delete();
        //     return redirect()->route('posts.index')->with('status', ['success' => $post->id . '番を削除しました']);
        // } else {
        //     return redirect()->route('posts.index')->with('status', ['danger' => '編集権限がありません']);
        // }

//deniesはallowsと逆の処理を行うことができます
        // if (Gate::denies('isPost', $post)) { // ! で否定文にしている。
        //     return redirect()->route('posts.index')->with('status', ['danger' => '編集権限がありません']);
        // } else {
        //     $post->delete();
        //     return redirect()->route('posts.index')->with('status', ['success' => $post->id . '番を削除しました']);
        // }

//削除権限のあるユーザーを取得
        $other_user = User::find($post->user_id);
//アクセスしたユーザではなく別のユーザーの権限でアクセス制限機能を使える。
        if(Gate::forUser($other_user)->allows('isPost',$post)){
            $post->delete();
            return redirect()->route('posts.index')->with('status', ['success' => $post->id . '番を削除しました']);
        }
    }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/758aac76-871e-3de9-f672-04e5288b27aa.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/0462c4a4-cde5-31d4-9295-63337b68a429.png)
Gate機能でmiddlewareでの制限もできる。trueのユーザーのみアクセスできる。
この場合は、管理者のみアクセスできるようにしている。

```php5:routes\web.php						
Route::middleware('can:is_admin')->group(function () {
Route::resource('/users', UserController::class);
});
```
</div></details>
編集の制限

```php5:app\Http\Controllers\PostController.php
    public function edit(Post $post)
    {
//+
        Gate::authorize('isPost', $post);
------------
    }
    public function update(PostRequest $request, Post $post)
    {
//+
        Gate::authorize('isPost', $post);

------------
    }
```
3.Policy(ポリシー)を用いたアクセス制限
---
Policyでは特定のモデルに関連する処理に対してアクセス制限を行なっていきます。
流れは、同じ、
非表示にして、
処理をさせない。
と
思ったが、違う、サーバー側の制限しかない。
非表示にするとかいう機能はない。

ウ～ン、下の画像のような時、一括して処理できていいと思う。
今回のケースでは適切ではないと思う policyは今回ながして、
ECサイトのデモサイトで採用する。今、[これ](https://www.amazon.co.jp/%E6%B0%97%E3%81%A5%E3%81%91%E3%81%B0%E3%83%97%E3%83%AD%E4%B8%A6%E3%81%BFPHP-%E6%94%B9%E8%A8%82%E7%89%88%EF%BC%8D%EF%BC%8D%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8C%E3%82%8B%E4%BA%BA%E3%81%AB%E3%81%AA%E3%82%8B%EF%BC%81-%E8%B0%B7%E8%97%A4%E8%B3%A2%E4%B8%80-ebook/dp/B08CXMPC3T/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=%E6%B0%97%E3%81%8C%E3%81%A4%E3%81%91%E3%81%B0%E3%83%97%E3%83%AD%E4%B8%A6%E3%81%BF&qid=1632659115&s=digital-text&sr=1-1)と[これ](https://www.amazon.co.jp/%E7%A2%BA%E3%81%8B%E3%81%AA%E5%8A%9B%E3%81%8C%E8%BA%AB%E3%81%AB%E3%81%A4%E3%81%8FPHP%E3%80%8C%E8%B6%85%E3%80%8D%E5%85%A5%E9%96%80-%E6%9D%BE%E6%B5%A6-%E5%81%A5%E4%B8%80%E9%83%8E-ebook/dp/B01M0X94BF/ref=sr_1_34?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=2Q3BIXM7JQDE0&dchild=1&keywords=php+%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&qid=1632659158&s=digital-text&sprefix=php+%2Cdigital-text%2C292&sr=1-34)をlaravelで書き換えている。
どちらとも本当に素晴らしい入門書だ。

https://blog.capilano-fw.com/?p=2650

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9006332d-8aac-8578-e240-41be05bd0fca.png)

###403ページを作成してみる。
以下にファイルを作成するだけ。
laravel/resources/views/errors/403.blade.php
404とかも、同じ。

```php5:resources\views\errors\403.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>403 Forbidden</title>
  <style>
    .error-wrap {
      padding: 5px 20px;
      border: 1px solid #dcdcdc;
      display: inline-block;
      box-shadow: 0px 0px 8px #dcdcdc;
    }
    h1 { font-size: 18px; }
    p { margin-left: 10px; }
  </style>
</head>
<body>
  <div class="error-wrap">
    <section>
      <h1>403 Forbidden</h1>
      <p>認証に失敗いたしました。</p>
    </section>
  </div>
</body>
</html>
```
はじめテンプレートサイトからコピペしたけど、記事が書けなくなったので、
変更しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/738eee4e-850a-c3ce-9029-35d88ad26ac2.png)

:::note info
非表示制限を解除して、非権限のbtnを押して見てください。
403ファイルが返ってきたらokです。
:::

コメント機能を実装する
---
posts->commentsテーブルは1対多の関係にある。
users->commentsテーブルも、1対多の関係にある。

```php5:app\Models\User.php　リレーション
    public function comments()
    {
        return $this->hasMany(Comment::class);
        //return $this->hasMany(Comment::class,'user_id','id');
    }
```
```php5:app\Models\Post.php　リレーション
//+
    public function comments()
    {
        return $this->hasMany(Comment::class);

        // return $this->hasMany(Comment::class,'post_id','id');
    }
```
```php5:app\Models\Comment.php　リレーション
//+
    public function post()
    {
        return $this->belongsTo(Post::class);

        // return $this->belongsTo(Post::class,'post_id','id');
    }
//+
    public function user()
    {
        // return $this->belongsTo(User::class);

        return $this->belongsTo(User::class,'user_id','id');
    }
//+
    protected $appends =[
        'diff_in_time',
    ];
    public function getDiffInTimeAttribute(){

        $updated_at = $this->updated_at;

        $diff_in_time = now()->diffInMinutes($updated_at);

        if($diff_in_time > 60*24){
            $diff_in_days =now()->diffInDays($updated_at);
            return $diff_in_days.'日前';
        }
        if($diff_in_time > 60){
            $diff_in_hours = now()->diffInHours($updated_at);
            return $diff_in_hours.'時間前';
        }

        return $diff_in_time.'分前';
    }
```
protected $appendsのおかげでこんなデータが簡単に使えるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/30a567ee-f142-1998-a194-f2dec101d559.png)

ルートを追加する

```php5:routes\web.php
Route::middleware(['auth'])->group(function () {
    Route::get('/', [App\Http\Controllers\PostController::class, 'index'])->name('posts.index');
    Route::resource('/posts', App\Http\Controllers\PostController::class,['except'=>'index']);
//+
    Route::resource('/comments', App\Http\Controllers\CommentController::class);
});
```
posts.show 画面にコメント機能を実装する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c2ae2a6d-9f96-4249-9991-f22d04d83e78.png)
posts\show.blade.php


PostController のshow()を編集する。
$postにcommentsとcomments.userを貼り付けてやる。

```php5:app\Http\Controllers\PostController.php

    public function show(Post $post)
    {
//+
//commentsとcomments.userでloadを使ってリレーションしている。
//commentだけではなく、commentのuserデータともリレーションできる。
        $post->load(['comments','comments.user']);
//dd($post)
        return view('posts.show', compact('post'));
    }

}
```	
しっかり、postデータに,postのcommentデータ、commentのuserデータが取得できている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/1d034cfd-4309-a30d-7eec-8a9af66748d0.png)

```html:resources\views\posts\show.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ $post->title }}</div>
                    <div class="card-body">
                        <p>カテゴリー：<a href="">{{ $post->category->name }}</a></p>
                        <p>{{ $post->content }}</p>
                        @if (isset($post->img_path))
                   {{--laravelのコンポーネント :imgPath="$post->img_path" で値を渡している。 --}}
                    <x-img_views :imgPath="$post->img_path"></x-img_views>
                        @endif
                    </div>
                    <div class="card-footer">
                        <p>投稿時間：{{ $post->created_at }}</p>
                        <a href="{{ route('posts.index') }}">戻る</a>

                        @can('isPost', $post)
                            <a class="btn btn-danger" href="{{ route('posts.edit', $post) }}">編集</a>
                            {{-- @else --}}
                        @endcan

                    </div>
                </div><-- /.card -->
{{-- +laravelのbladeコンポーネント  --}}
{{-- +コメントの作成フォーム  --}}
                <x-comment.create :post="$post" />
{{-- +コメントの一覧画面  --}}
                <x-comment.index :post="$post" />
            </div>
        </div>
    </div>
@endsection
```
bladeコンポーネントは resources\views\componentsに、
*.blade.phpファイルを作成すればいいだけ、めちゃお手軽。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/e386eded-3f24-fa34-8a7d-f2f07f39649c.png)


```js:
//上記のデータをオブジェクト化するといい感じになる。
<script>
post = @json($post);
console.log(post);
</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/596d335f-3b7b-dc22-36c9-21e29950b7e2.png)

```js:
<script>
post = @json($post);
console.log(post);
//リレーション先のデータも簡単に取得できる。
console.log(post.category.name);
console.log(post.comments[0].content);
console.log(post.comments[0].user.name);
console.log(post.comments[1].content);
console.log(post.comments[1].user.name);
</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/8769e194-2056-7ced-f643-1c928ae47373.png)

コメントはajaxでSPA CRUDにしてみた。
jqueryでvueっぽく作ってみた。

```js:
//vueのdataみたいにオブジェクトとしておいておく。
        comments = @json($post->comments);
        console.log(comments);
```
commentsをvueのdataのように、利用できる。
crudする度に、commentsを修正して、getCommentsでhtmlを再形成している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/7fc690d6-b57a-a6bd-69ec-436b0b7edaeb.png)
CommentControllerを編集する。


```php5:app\Http\Controllers\CommentController.php
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use Illuminate\Http\Request;
use App\Models\Post;

class CommentController extends Controller
{
    // public function index(){}

    // public function create(){}
//+
    public function store(Request $request)
    {
        $request->merge(['user_id'=>auth()->id()]);
//create()の返り値は作成したデータが返ってくる滅茶苦茶便利。
//appends[]のdiff_in_timeも返ってくる最高ー。
        $comment = Comment::create($request->all());
//modelがget()前なら、with()でリレーションしてからget()!
//modelがget()後なら,load()でリレーションするだけ。
// cereate()はget()後のデータを返すので、load()を使う。
        $comment->load('user');

        return $comment;
    }

    // public function show(Comment $comment){}

    // public function edit(Comment $comment){}
//+
    public function update(Request $request)
    {
//updateOrCreate()の返り値は作成したデータが返ってくる滅茶苦茶便利。
//update()はupdateした数が返ってくるから、使うことはない。
//appends[]のdiff_in_timeも返ってくる最高ー。
        $comment = Comment::updateOrCreate(['id'=>$request->id],$request->all());
        $comment->load('user');
        return $comment;
    }

    public function destroy(Request $request)
    {
        Comment::find($request->id)->delete();
        return 'success';
    }
}
```
commentsオブジェクトから、html要素を作成してbodyに貼り付けただけ。

```php5:resources\views\components\comment\index.blade.php
@push('js')
    <script>
//親元のコンポーネントにデータを置いておけば、
//子コンポーネントにわざわざ渡さたなくても利用できる。
        comments = @json($post->comments);
        auth_id = {{ auth()->id() }};
        post_id_page = {{$post->id}};

//これで、commentsデータをhtml化して上に貼り付けている。

        if (comments.length > 0) {
            getComments(comments);
        }

        $(document).on('click', '.delete_btn', function() {
            comments_index = $(this).parents('.card').data('comments_index');
            comment_id = comments[comments_index].id;
            $.ajax({
                url: "{{ route('comments.destroy', 0) }}",
                type: 'DELETE', //DELETE送信
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                data: {
                    id: comment_id
                },
                timeout: 6000, //タイムアウトの設定
            }).done(function() {
                comments.splice(comments_index,1);
                getComments(comments);
                comments_index='';
            }).always(function() {
                comment_id = '';
            });
        });


        function getComments(comments) {
            // console.log('＼(^o^)／');

            $result = $("#comment_cards"),
            comment_cards = [];
            $.each(comments, function(index, comment) {
                comment_card =
                    `<div id="comment_id_${comment.id}" data-comments_index="${index}" class="card mt-3">
                        <div class="card-header bg-white py-1 row no-gutters align-items-center">
                            <strong class="text-muted">${ comment.user.name }</strong>`;
                if(comment.user_id == auth_id ){
                comment_card +=
                    `<a href="javascript:void(0)" class="badge badge-pill badge-warning position-absolute text-danger"
                        style="right:200px;top:-10px; padding:10px;font-size:15px" id="edit_form_btn" data-toggle="modal">
                        編集
                    </a>
                    <a href="javascript:void(0)" class="delete_btn badge badge-pill badge-danger position-absolute text-warning"
                        style="right:100px;top:-10px; padding:10px;font-size:15px">
                        削除
                    </a>`;
                }
                comment_card +=
                    `<small class="text-muted ml-auto diff_in_time">${ comment.diff_in_time }</small>
                    </div>
                    <div class="card-body content">
                                ${ comment.content }
                    </div>
                    </div>`;
                comment_cards.push(comment_card);
            });
            $result[0].innerHTML = comment_cards.join("");
        }

    </script>
@endpush
```
```php5:resources\views\components\comment\create.blade.php
<div class="card mt-2">
    <div class="card-body pb-1">
        <div class="form-group row no-gutters">
            <label for="content" class="col-6 col-form-label">コメント</label>
            <div class="col-6 text-right">
                <button id="create_form_btn" type="submit" class="btn btn-primary">
                    コメント
                </button>
            </div>

            <div class="col-12 mt-2">
                <textarea id="create_content" name="content" class="form-control mb-2">
                        
                </textarea>
            </div>
        </div>
    </div>
</div>
@push('js')
    <script>
        $(document).on('click', '#create_form_btn', function() {
            content = $('#create_content').val();

            $.ajax({
                url: "{{ route('comments.store') }}",
                type: 'POST',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                data: {
                    'content': content,
                    'post_id': post_id_page,
                },
                timeout: 6000, //タイムアウトの設定
            }).done(function(data) {
               $('#create_content').val('');
//返り値をcommentsに追加
               comments.push(data);
//getComments(comments);でhtmlを再形成
               getComments(comments);
            }).always(function(){
                content = '';
            });
        });
    </script>
@endpush
```
```php5:resources\views\components\comment\edit.blade.php
<!-- モーダル -->
<div class="modal" id="edit_form" tabindex="-1" role="dialog" aria-labelledby="edit_formlLabel"
    aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <!-- モーダルのヘッダー -->
            <div class="modal-header">
                <!-- モーダルのタイトル -->
                <h5 class="modal-title" id="edit_formlLabel">コンテンツ編集</h5>
                <!-- 閉じるアイコン -->
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                        aria-hidden="true">&times;</span></button>
            </div>
            <!-- モーダルのボディー -->
            <div class="modal-body row no-gutters">
                <textarea name="content" id="edit_content" class="col-12"></textarea>
                <button type="button" id="edit_content_btn" class="btn btn-warning ml-auto mt-2"
                    data-dismiss="modal">編集</button>
            </div>
        </div>
    </div>
</div>
</div>

@push('js')
    <script>
        $(document).on('click', '#edit_form_btn', function() {
            card_data = $(this).parents('.card');
            p = card_data.find('.card-body').text().trim();
            comment_index = card_data.data('comments_index');
            comment = comments[comment_index];
            console.log(comment);

            $('#edit_form').modal('show');
            $('#edit_content').val(p).focus();
        });

        $(document).on('click', '#edit_content_btn', function() {
            content = $('#edit_content').val();
            comment_id = comment.id;
            post_id = comment.post_id;
            user_id = comment.user_id;
            $.ajax({
                url: "{{ route('comments.update', 0) }}",
                type: 'PUT',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                data: {
                    'id': comment_id,
                    'content': content,
                    'post_id': post_id,
                    'user_id': user_id,
                },
                timeout: 6000, //タイムアウトの設定
            }).done(function(data) {
                //commentsのデータを返り値で上書きする。
                comments[comment_index] = data;
                //上書きしたcommentsでhtmlを再形成している。
                getComments(comments);
                edit_data_id = '';
                comment='';
                console.log('comment:', comment);
                console.log('comments', comments[comment_index]);
            }).always(function() {
                card_data = '';
                p = '';
                content = '';
                comment_id = '';
                comment_data = '';
                post_id = '';
                user_id = '';
            });
        });
    </script>
@endpush
```
[posts.index](https://localhost/laravel_create_board_by_riku/public/)

:::note info
ここで、commentsのCRUDの確認。投稿、編集、削除の確認。
:::

###ajax送信時のバリデーション機能を追加する。
errorの返り値をどうhtmlに反映させる。
フォームリクエストの作成

```
php artisan make:request CommentRequest
```
```php5:app\Http\Requests\CommentRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CommentRequest extends FormRequest
{
    public function authorize()
    {
//trueに変更
        return true;
    }
    public function rules()
    {
        return [
//+
            'content' =>['required'],
        ];
    }
}
```
```php5:app\Http\Controllers\CommentController.php
//+
use App\Http\Requests\CommentRequest;
//修正
    public function store(CommentRequest $request)
    public function update(CommentRequest $request)
```
```js:エラーの返り値の取得
//+
.fail(function(jqXHR, textStatus, errorThrown) {
           console.log(jqXHR.responseJSON.errors)
        })
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/1ad70b0f-f4e3-08bb-5fd8-19f83951d8be.png)

```js:resources\views\components\comment\create.blade.php
@push('js')
    <script>
        $(document).on('click', '#create_form_btn', function() {
            content = $('#create_content').val();

            $.ajax({
                url: "{{ route('comments.store') }}",
                type: 'POST',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                data: {
                    'content': content,
                    'post_id': post_id_page,
                },
                timeout: 6000, //タイムアウトの設定
            }).done(function(data) {
                $('#create_content').val('');

//+error関係を消去している。
                $('#create_content').removeClass('is-invalid');
                $.each($('.error_msg'),function(index,element){
                    element.remove();
                });

                comments.push(data);
                getComments(comments);
//+
            }).fail(function(jqXHR) {
                errors = jqXHR.responseJSON.errors;
                console.log(errors);
                $.each(errors, function(error, error_msgs) {
                    $.each(error_msgs, function(index, error_msg) {
                        err =`<span class="error_msg text-danger d-block"><strong>${error_msg}</strong></span>`
                        $(`[name="${error}"]`).after(err);
                    })
                })
                $('#create_content').addClass('is-invalid')

            }).always(function() {
                content = '';
            });
        });
    </script>
@endpush
```
:::note info
空のリクエストして、バリデーションを確認する。
validationに 'min:1'を追加して複数のバリデーションエラーを確認する。
'content' =>['min:1','required',],
:::


タグ機能を実装する。
---
このアプリで一番面白いところだと思う。
ポストに複数のタグを貼り付けて、検索できるようにする。
検索機能は後回しにして、タグ付けできるようにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/08051f00-9069-9cb9-471c-4e1713c6dcc8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/e918c2cd-eea4-02c4-7ff7-b93faac0f5ec.png)

```php5:ロジックの流れ
// $request->contentから正規表現で#tagを抽出
//結果を$match[1]の配列で取得できる。
            preg_match_all('/#([a-zA-Z0-9０-９ぁ-んァ-ヶー一-龠]+)/u', $request->content, $match);

//dd($match);//画像上

//tag_idを保管する箱
        $tag_ids = [];

//マッチした配列をtagsテーブルに保存
        foreach ($match[1] as $tag) {
            //同名の名前がなければ、createして、createしたデータを返してくれる。
            $tag = Tag::firstOrCreate(['tag_name' => $tag]);

//createしたidを配列化する。
            array_push($tag_ids, $tag->id);
        }

//post_tag(中間)テーブルに保存する関数を使って先程のids配列を保存する。
//https://www.wakuwakubank.com/posts/387-laravel-relation-3/#%E5%A4%9A%E5%AF%BE%E5%A4%9A%E9%96%A2%E4%BF%82%E3%81%AE%E7%B4%90%E4%BB%98%E3%81%91
            $post->tags()->attach($tag_ids);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/a2b1337c-0c1d-922f-63c5-93d540a9c13c.png)


tagを保存するTagモデルとtagsテーブルを作成する。

```
php artisan make:model Tag -a
```
```php5:database\migrations\XXXX_create_tags_table.php
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
//+
            $table->string('tag_name');
            $table->timestamps();
        });
    }
```
```php5:app\Models\Tag.php
class Tag extends Model
{
    use HasFactory;
//+
    protected $fillable = [
        'tag_name',
    ];
}
```
postsとtagsは多対多の関係であるため、中間テーブルを作成する。
テーブル名はlaravelの命名規則ではアルファベット順で単数形

```:
php artisan make:migration create_post_tag_table
```
```php5:database\migrations\xxxxx_create_tag_post_table.php
        Schema::create('tag_post', function (Blueprint $table) {
            $table->id();

            $table->foreignId('post_id')
            ->constrained()
            ->cascadeOnDelete()
            ->cascadeOnUpdate();
            $table->foreignId('tag_id')
            ->constrained()
            ->cascadeOnDelete()
            ->cascadeOnUpdate();

            $table->timestamps();
        });
```
```
php artisan migrate
```
Postモデルの多対多のリレーション

```php5:app\Models\Post.php
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }
```
Postコントローラーのstore()を修正する。

```php5:app\Http\Controllers\PostController.php
//+
use App\Models\Tag;

    public function store(PostRequest $request)
    {
        if ($request->has('cutomfile')) {
            $img_path = uniqid("tmp_img_") . "/";
            $request->merge(
                ['img_path' => $img_path],
            );
            foreach ($request->file('cutomfile') as $key => $img_file) {
                //ファイルが存在しているかに付け加え、isValidメソッドで問題なくアップロードできたのかを確認できたなら
                if ($img_file->isValid()) {
                    //画像なら
                    if (Str::is('image*', $img_file->getMimeType())) {
                        //pathを作成(path/+ ファイル名(空白は '_' に置き換えている))
                        $img_files_path = $img_path . Str::of($img_file->getClientOriginalName())->replace(' ', '_');
                        // storage\appからの相対パス 画像を保存している。//取得はsrc="{{ asset('storage/'.$tmp_file)
                        $img_file->storeAs("public", $img_files_path);
                        $img_files_path = '';
                    }
                }
            }
        }
        $request->merge(
            ['user_id' => Auth::id()]
        );

//+正規表現でマッチするデータを配列で取得
        preg_match_all('/#([a-zA-Z0-9０-９ぁ-んァ-ヶー一-龠]+)/u', $request->content, $match);

        $tag_ids = [];
        foreach ($match[1] as $tag) {
//同名の名前がなければ、createして、createしたデータを返してくれる。
            $tag = Tag::firstOrCreate(['tag_name' => $tag]);
            array_push($tag_ids, $tag->id);
        }

//修正
        $post = Post::create($request->except('cutomfile'));
//+ リレーションすることで、中間テーブルにデータを保存する。
        $post->tags()->attach($tag_ids);

        return redirect()->route('posts.index')->with('status', ['success' => '投稿完了']);
    }
```
tagsテーブルとpost_tagにしっかりと保存されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9c06cbd2-8ebb-f500-ae42-ef5a595cffad.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/b45fbca9-7ea2-5fc9-3fe4-8bb8b541a433.png)
###タグを表示する。
```php5:resources\views\posts\index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <x-flashMessage />
                <div class="card">
                    <div class="card-header d-flex justify-content-between">
                        <span>投稿一覧</span>
                        <a href="{{ route('posts.create') }}">投稿</a>
                    </div>
                    <div class="card-body overflow-auto">
                        @foreach ($posts as $post)
                            <div class="
                            card mt-1">
                                <div class="card-body">
                                    <h5 class="card-title">{{ $post->title }}</h5>
{{-- テーブルに修正した。 --}}
                                    <table class="table table-hover">
                                        <tr>
                                            <th>投稿者</th>
                                            <td><a href="" class="card-text">{{ $post->user->name }}</a></td>
                                        </tr>
                                        <tr>
                                            <th>カテゴリー</th>
                                            <td><a href="" class="card-text">{{ $post->category->name }}</a></td>
                                        </tr>
                                        <tr>
                                            <th>タグ</th>
                                            <td class="">
                                                @foreach ($post->tags as $tag)
                                                    <a href="" class="card-text mr-1">#{{ $tag->tag_name }}</a>
                                                @endforeach
                                        </tr>
                                    </table>

                                    <p class="card-text mt-1">{!! nl2br(e($post->content)) !!}</p>
                                    <div class="d-flex justify-content-between">
                                        <a href="{{ route('posts.show', $post) }}" class="btn btn-primary">詳細 コメントを投稿</a>

                                        @can('isPost', $post)
                                        {{-- @isPost($post) --}}
                                        <a class="btn btn-danger " href="javascript:void(0)"
                                            onclick="deletePost({{ $post->id }}); this.onclick=null;">
                                            削除
                                        </a>
                                        {{-- @endisPost --}}
                                        @endcan
                                    </div>
                                </div>
                            </div>
                        @endforeach
                        <form action="" method="POST" id="delete_form" class="d-none">
                            @csrf
                            @method('delete')
                        </form>
                        </form>
                    </div>
                    {{ $posts->links() }}
                </div>
            </div>
        </div>
    </div>
@endsection
@push('js')
    <script>
        function deletePost(post_id) {
            url = `{{ route('posts.destroy', '') }}/${post_id}`;
            $('#delete_form').attr('action', url).submit();
        }
    </script>
@endpush
```
タグが表示され、テーブルにしたことで見た目も改善された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/ac0162e6-abd7-f2f0-aed3-1455e446187c.png)
クエリが繰り返し発行されている。N+1問題が生じている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/b960e8fc-25ae-ab0a-a184-083247180dbf.png)

```php5:
    public function index()
    {
        // $posts = Post::with(['category','user'])->get();
//tagsを追加するだけ。
        $posts = Post::with(['category', 'user','tags'])->paginate(3);

        // dd($posts);
        return view('posts.index', compact('posts'));
    }
```
`select 省略 from tags` の発行回数が３回から１回になった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9de5bf1d-59f9-26c3-a84a-98da8cf554d6.png)
名前、カテゴリー、タグ、テキスト検索機能を実装する。
---
###投稿者検索
投稿者のリンクから、ユーザーの**投稿一覧**と**コメント一覧**を取得する。
user.showを作成する。
・プロフィール
・投稿一覧
・コメント一覧
ルートを作成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/0531f61a-c69f-9a98-376c-31dee514392b.png)

```php5:routes\web.php
Route::middleware(['auth'])->group(function () {
    Route::get('/', [App\Http\Controllers\PostController::class, 'index'])->name('posts.index');
    Route::resource('/posts', App\Http\Controllers\PostController::class,['except'=>'index']);
    Route::resource('/comments', App\Http\Controllers\CommentController::class);

//+
    Route::resource('/users', App\Http\Controllers\UserController::class);
});
```
```php5:resources\views\posts\index.blade.php
{{-- テーブルに修正した。 --}}
                                    <table class="table table-hover">
                                        <tr>
                                            <th>投稿者</th>
{{-- +{{ route('users.show',$post->user) }} --}}
                                            <td><a href="{{ route('users.show',$post->user) }}" class="card-text">{{ $post->user->name }}</a></td>
                                        </tr>
                                        <tr>
                                            <th>カテゴリー</th>
{{-- +{{ route('categories.show',$post->category) }} --}}
                                            <td><a href="{{ route('categories.show',$post->category) }}" class="card-text">{{ $post->category->name }}</a></td>
                                        </tr>
                                        <tr>
                                            <th>タグ</th>
                                            <td class="">
                                                @foreach ($post->tags as $tag)
{{-- +{{ route('tags.show',$tag) }} --}}
                                                    <a href="{{ route('tags.show',$tag) }}" class="card-text mr-1">#{{ $tag->tag_name }}</a>
                                                @endforeach
                                        </tr>
                                    </table>
```
UserControllerを作成する。

```
php artisan make:controller UserController
```
```php5:app\Http\Controllers\UserController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

//+
use App\Models\User;

class UserController extends Controller
{
    public function show(User $user)
    {
//N+1対策 $user(1)->comments(多),  $comment(1)->post(1)
//                        　　　　   ↑(ココでN+1問題が生じる 1対1を繰り返すため)
         $user = $user->load(['comments.user','comments.post']);

//苦肉の策 $user = $user->load(['posts.comments'])->loadCount('posts.comments')が出来なかった。
//$user->posts()でリレーション先のpostモデルに変換している。
// dd($user->load('posts')); //----＼(^o^)／画像を見てくれ
// dd($user->posts()->get());       //----(^O^)　画像を見てくれよ
        $posts = $user->posts()->withCount('comments')->get();
        return view('users.show', compact('user', 'posts'));
    }
}
```
`dd($user->load('posts'));`
Userモデルは変更されずに、リレーションとしてpostsを取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/daf817bf-a29c-0ecd-851d-1ce0f033dd82.png)
UserモデルからPostモデルへ変換されてpostsを取得 <font color="red">モデルが変わっている</font>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/c27ab0fc-7390-64a6-314e-0225c3fb8741.png)

```php5:式で書くと
$user = $user->load('posts');
$post = $user->posts()->get();
```
```php5:ちなみに
dd($user->posts();
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/8d69ec0e-3ace-3737-713c-bd2222a0034e.png)

```php5:
dd($user->posts()->getParent());  //----そのままuserモデル
dd($user->posts()->get());       //----ポストモデルに変換
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/60f3b4c3-6753-f3e3-16e0-a2d3ee523d94.png)
補足画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/5d5a8ee9-b90e-2792-36b1-ff7680b96a0f.png)
**`create:`**users.show.blade.phpの作成
https://v4.bootstrap-guide.com/javascript/tabs/active

```php5:resources\views\users\show.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ $user->name }}</div>
                    <div class="card-body">
                        <x-flashMessage></x-flashMessage>
                        <!-- タブ部分 -->
                        <ul id="myTab" class="nav nav-tabs" role="tablist">
                            <li class="nav-item" role="presentation">
                                <a href="#home" id="home-tab" class="nav-link active" role="tab" data-toggle="tab"
                                    aria-controls="home" aria-selected="true">プロフィール</a>
                            </li>
                            <li class="nav-item" role="presentation">
                                <a href="#profile" id="profile-tab" class="nav-link" role="tab" data-toggle="tab"
                                    aria-controls="profile" aria-selected="false">投稿一覧</a>
                            </li>
                            <li class="nav-item" role="presentation">
                                <a href="#contact" id="contact-tab" class="nav-link" role="tab" data-toggle="tab"
                                    aria-controls="contact" aria-selected="false">コメント一覧</a>
                            </li>
                        </ul>

                        <!-- パネル部分 -->
                        <div id="myTabContent" class="tab-content mt-3">
                            <div id="home" class="tab-pane active" role="tabpanel" aria-labelledby="home-tab">
                                <ul class="list-group list-group-flush">
                                    <li class="list-group-item">ID : {{ $user->id }}</li>
                                    <li class="list-group-item">NAME :{{ $user->name }}</li>
                                    <li class="list-group-item">EMAIL:{{ $user->email }}</li>
                                </ul>

                            </div>

                            <div id="profile" class="tab-pane" role="tabpanel" aria-labelledby="profile-tab">
                                <x-posts.index :posts="$posts" />
                            </div>

                            <div id="contact" class="tab-pane" role="tabpanel" aria-labelledby="contact-tab">
                                <x-comment.index :post="$user" type="userShow" />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

```
**`create:`**components\posts\index.blade.phpの作成

```php5:resources\views\components\posts\index.blade.php
    <table class="table table-sm">
        <thead>
            <tr>
                <th>タイトル</th>
                <th class="text-center">コメント数</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($posts as $post)
                <tr>
                    <td>
                        <a href="{{ route('posts.show', $post) }}" class="text-secondary list-group-item border-0">
                            {{ $post->title }}
                        </a>
                    </td>
                    <td class="text-center">
                        <a href="" class="text-secondary list-group-item border-0">{{ $post->comments_count }}</a>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
```
**`edit:`**components\comment\index.blade.phpの編集

```php5:resources\views\components\comments\index.blade.php
<div id="comment_cards">

</div>

<x-comment.edit />

@push('js')
    <script>
//親元のコンポーネントにデータを置いておけば、
//子コンポーネントにわざわざ渡さたなくても利用できる。
        comments = @json($post->comments);
        auth_id = {{ auth()->id() }};
        post_id_page = {{$post->id}};

        //+
        post_uri = "{{ route('posts.show', '') }}";

//これで、commentsデータをhtml化して上に貼り付けている。

        if (comments.length > 0) {
            getComments(comments);
        }

        $(document).on('click', '.delete_btn', function() {
            comments_index = $(this).parents('.card').data('comments_index');
            comment_id = comments[comments_index].id;
            $.ajax({
                url: "{{ route('comments.destroy', 0) }}",
                type: 'DELETE', //DELETE送信
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                data: {
                    id: comment_id
                },
                timeout: 6000, //タイムアウトの設定
            }).done(function() {
                comments.splice(comments_index,1);
                getComments(comments);
                comments_index='';
            }).always(function() {
                comment_id = '';
            });
        });


        function getComments(comments) {
            // console.log('＼(^o^)／');

            $result = $("#comment_cards"),
            comment_cards = [];
            $.each(comments, function(index, comment) {
                comment_card =
                    `<div id="comment_id_${comment.id}" data-comments_index="${index}" class="card mt-3">
                        <div class="card-header bg-white py-1 row no-gutters align-items-center">
                            <strong class="text-muted">${ comment.user.name }</strong>`;
                if(comment.user_id == auth_id ){
                comment_card +=
                    `<a href="javascript:void(0)" class="badge badge-pill badge-warning position-absolute text-danger"
                        style="right:200px;top:-10px; padding:10px;font-size:15px" id="edit_form_btn" data-toggle="modal">
                        編集
                    </a>
                    <a href="javascript:void(0)" class="delete_btn badge badge-pill badge-danger position-absolute text-warning"
                        style="right:100px;top:-10px; padding:10px;font-size:15px">
                        削除
                    </a>`;
                }
                comment_card +=
                    `<small class="text-muted ml-auto diff_in_time">${ comment.diff_in_time }</small>
                    </div>
                    <div class="card-body content">
                                ${ comment.content }
                    </div>
                    </div>`;
                comment_cards.push(comment_card);
            });
            $result[0].innerHTML = comment_cards.join("");
        }

        //+
@isset($type)
@if ($type=='userShow')

        $(document).on('mouseenter',".card-body.content", function() {
            $(this).css('cursor', 'pointer');

            $(document).on('click', ".card-body.content", function() {
            comments_index = $(this).parents("[id ^= 'comment_id_' ]").data('comments_index');
            post_id = String(comments[comments_index].post_id);
            // console.log(comment_id);
            location.href = `${post_uri}/${post_id}`;
        });
            });
@endif
@endisset
    </script>
@endpush

```

:::note info
投稿者のリンクから
・投稿者のプロフィール
・投稿一覧
・コメント一覧
を確認する。
:::

###カテゴリー検索
カテゴリーの検索から、ポスト一覧を取得する。
categories.show()を作成する。
ルートは追加する。

```php5:routes\web.php

//+
    Route::resource('/categories',App\Http\Controllers\CategoryController::class);
```
CategoryControllerを作成する。

```
php artisan make:controller CategoryController -r
```
```php5:
//先程、孫のloadCount()ができなかったので(´・ω・｀)してたら、できた。
        $category->load('posts')->loadCount('posts.comments');//これは駄目。
//モデルでWithCount()してやればいいだけだった。
      return $this->hasMany(Post::class,'category_id','id')->withCount('comments');
```
```php5:app\Models\Category.php
    public function posts()
    {
//修正->withCount('comments')を追加
        return $this->hasMany(Post::class,'category_id','id')->withCount('comments');
    }
```
```php5:app\Http\Controllers\CategoryController.php
    public function show(Category $category)
    {
//N+1問題は生じないためloadは必要がない。
//$category->posts $categoryで繰り返しが生じていないため。
        return view('categories.show',compact('category'));
    }
```
`create:`resources\views\categories\show.blade.php

```php5:resources\views\categories\show.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ $category->name }}</div>

                    <div class="card-body">
                        <x-flashMessage />

                        <x-posts.index :posts="$category->posts" />

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
ブックカテゴリー一覧が取得できている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/8a6ee8c1-4b97-06f4-5cae-6d66534ed5ad.png)

#####カテゴリーの追加、編集、削除を実装する。
カテゴリーを削除したら、相手先の
postsのcategory_idはnull値に変更する。
今のテーブル構造では
categoryが変更されたら、リレーション先も変更されるし、
変更：categoryが削除されたらリレーション先のpostも削除される。
変更：category_idはnull値が禁止されている。

```nullOnDelete()のきまりコード
$table->foreignId('foreign_id')->nullable()->constrained("table_name")->cascadeOnUpdate()->nullOnDelete();
```
変更前
カテゴリーIDを変更してみる。

```
php artisan tinker
```
```
$category = Category::find(1);
```
```
$category->update(['id'=>10]);
```
あれ、変更されない。なんでや？
変更された。update()やfill()では主キーのidの更新はできないみたい。

```:生のSQLで確認してみた。
 DB::update('update categories set id = 10 where id = 1');
```
categoriesのidを1から10に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/200d1acb-22ae-92ca-6750-9e9bb8e61f48.png)
リレーション先postsのcategory_idも1から10に変更されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/985a743c-9629-fcc6-60e4-061b5b1d94a1.png)
削除してみる。

```
$category = Category::find(1);
$category ->delete();
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/122de93f-48a0-c2b6-b160-7a7d730964c0.png)
postsのcategories_id 10番も全部消えた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/ae76c24d-4c99-1707-2882-cfd64d53ab64.png)
↑これを消去するのではなく、null値に変更するだけ。
外部キー関係はあとからテーブル変更するのは大変なので、
foreign_keyが残るためとか、色々原因がある。ので、作り直した方が
絶対にいいと思う。くだらんことで時間を無駄にしたらアカン。
テーブルを再編集する。

```php5:database\migrations\XXXXX_create_posts_table.php

    public function up()
    {
        //外部キー制約を無効
        Schema::disableForeignKeyConstraints();
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            //not nullableでﾘﾚｰｼｮﾝ先のrecordが更新されたら更新、削除されたら削除
            $table->foreignId('user_id')
                ->constrained()
                ->cascadeOnDelete()
                ->cascadeOnUpdate();
            $table->foreignId('category_id')
                ->nullable()
                ->constrained()
                ->nullOnDelete()
                ->cascadeOnUpdate();
            $table->string('title');
            $table->text('content');
            $table->timestamps();
        });
        //外部キー制約を有効
        Schema::enableForeignKeyConstraints();
    }
```
```
php artisan migrate:fresh
```
```データを消去したため、
php artisan db:seed
```
```
php artisan tinker
```
```
$category = Category::find(1);
$category ->delete();
```
```
Post::where('category_id',null)->count();
```
```
Post::where('category_id',null)->get();
```
今度は削除されずに null値　がセットされた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/7ffb975b-899f-bd44-0467-cc8cf9180050.png)
null値になったのでエラーが出たので、デフォルト値を設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/72ef980b-2227-d46c-f3e3-c4a7d836a397.png)

```php5:app\Models\Post.php
    public function category()
    {
//デフォルト値を設定
        return $this->belongsTo(Category::class,'category_id','id')->withDefault([
           'id'=>'', 'name' => 'なし',
        ]);
    }
```
エラーが回避された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/09386f73-7d37-f39d-de26-d9b901eb161a.png)
これで、カテゴリーの追加と編集と削除の準備が整ったので、実装していく。
###カテゴリーを追加する。
categories.create と categories.storeの作成
リンクを作成する

```php5:resources\views\layouts\app.blade.php

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">
<!-- + -->
                      <li>
                           <a class="btn btn-primary" 
                              href="{{ route('categories.create') }}">
                              カテゴリー
                           </a>
                      </li>
                   </ul>
```
```php5:app\Http\Controllers\CategoryController.php
//+
    public function create()
    {
        $categories = Category::all();
        return view('categories.create', compact('categories'));
    }
//+
    public function store(Request $request)
    {
        Category::create($request->all());
        return redirect()->route('categories.create')->with('status',['success'=>'カテゴリーを追加しました。']);
    }
```
```php5:resources\views\categories\create.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-10">
                <x-flashMessage></x-flashMessage>
                <div class="card">
                    <div class="card-header">カテゴリーを追加</div>

                    <div class="card-body">

                        <x-categories.from/>

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
コンポーネントを作成する
resources\views\components\categories\
・form.blade.php

```php5:resources\views\components\categories\form.blade.php
@php
    $url = route('categories.create');
    $method = 'POST';
@endphp

<form method="POST" action="{{ $url }}" novalidate>
    @method($method)
    @csrf
    <div class="form-group row no-gutters">
        <label for="name" class="col-form-label text-md-right">カテゴリーネーム</label>
        <button type="submit" class="btn btn-primary ml-auto">
            追加
        </button>
        <div class="col-12 mt-2">
            <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name"
                value="{{ old('name', $category->name??'') }}" required autocomplete="name" autofocus>

            @error('name')
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $message }}</strong>
                </span>
            @enderror
        </div>
    </div>
</form>
```
バリデーションを実装する。

```php5:app\Http\Controllers\CategoryController.php
//+
use Illuminate\Support\Facades\Validator;

    public function store(Request $request)
    {
//+
        //validateメソッドによるへリダイレクト
        $validator = Validator::make($request->all(), [
            'name' => ['required', 'unique:categories'],
        ]);
        if ($validator->fails()) {
            return redirect()->route('categories.create')
                ->withErrors($validator)
                ->withInput();
        }

        Category::create($request->all());
        return redirect()->route('categories.create')->with('status', ['success' => 'カテゴリーを追加しました。']);
    }
```
編集と削除　categories.edit()とcategories.deleteを実装する

```php5:app\Http\Controllers\CategoryController.php
//+
    public function edit(Category $category)
    {

        return view('categories.edit', compact('category'));
    }
//+
    public function update(Request $request, Category $category)
    {
        //validateメソッドによるへリダイレクト
        $validator = Validator::make($request->all(), [
            'name' => ['required', 'unique:categories'],
        ]);

        if ($validator->fails()) {
            return redirect()->route('categories.edit',$category)
                ->with('status', ['danger' => 'バリデーションに失敗しました。'])
                ->withErrors($validator)
                ->withInput();
        }
        Category::updateOrCreate(['id' => $category->id], $request->all());
        return redirect()->route('categories.create')->with('status', ['success' => 'カテゴリーを編集しました。']);
    }
//+
    public function destroy(Category $category)
    {
        $category->delete();
        return redirect()->route('categories.create')->with('status', ['success' => 'カテゴリーを削除しました。']);
    }
```
編集ボタンと削除ボタン、jsの追加

```php5:resources\views\categories\create.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-10">
                <x-flashMessage></x-flashMessage>
                <div class="card">
                    <div class="card-header">カテゴリーを追加</div>

                    <div class="card-body">

                        <x-categories.from/>
{{-- ＋ --}}
                        <x-categories.index :categories="$categories"/>

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
```php5:resources\views\categories\edit.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-10">
                <x-flashMessage></x-flashMessage>
                <div class="card">
                    <div class="card-header">カテゴリーを編集</div>
                    <div class="card-body">
                        <x-categories.form :category="$category" type="edit"/>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
コンポーネントを作成する
resources\views\components\categories\
・index.blade.php
・(修正)form.blade.php

```php5:resources\views\components\categories\index.blade.php
<table class="table">
    <tbody>
        @foreach ($categories as $category)
            <tr>
                <td class="w-50">
                    <a  class="list-group-item" href="{{ route('categories.show', $category) }}">
                            {{ $category->name }}
                        </a>
                </td>
                <td class="w-25">
                    <a class="list-group-item"
                        href="{{ route('categories.edit', $category) }}">編集</a>
                    </td>
                <td class="w-25">

                    <a class="list-group-item" href="javascript:void(0)"
                        onclick="deleteCategory({{ $category->id }}); this.onclick=null;">
                        削除
                    </a>
                </td>
            </tr>
        @endforeach
    </tbody>
</table>

<form id="delete-form" action="" method="POST" class="d-none">
    @method('delete')
    @csrf
</form>

@push('js')
    <script>
        url = "{{ route('categories.destroy','') }}"
        function deleteCategory(category_id) {
            $('#delete-form').attr('action',`${url}/${category_id}`).submit();
        }
    </script>
@endpush
```
```php5:resources\views\components\categories\form.blade.php
@php
//修正
if (isset($type) == 'edit') {
    $url = route('categories.update',$category);
    $method = 'PUT';
}else{
    $url = route('categories.create');
    $method = 'POST';
}
@endphp
```

[categories.create](https://localhost/laravel_create_board_by_riku/public/categories/create)
categoriesのCRUDとバリデーションを確認する

###タグ検索機能を実装する。
tags.showを作成して、posts一覧を取得する。
ルートを追加する。

```php5:routes\web.php
//+
Route::resource('/tags', App\Http\Controllers\TagController::class);
```

```php5:app\Models\Tag.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
    use HasFactory;
    protected $fillable = [
        'tag_name',
    ];
//+ 多対多のリレーション
    public function posts(){
         return $this->belongsToMany(Post::class)->withCount('comments');

        //2番目の引数は、中間テーブル名
        //3番目の引数は、検索する外部キー,$tag->posts() tag_idでpostsを検索している。
        //4番目の引数は、関連付けるモデルの外部キー名 post_idと紐付けろ
                                                 //(例えばuser_idではなく)。
        // return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
        // return $this->belongsToMany(Post::class,'post_tag','tag_id','post_id');
    }
}
```
Tagcontroller

```php5:app\Http\Controllers\TagController.php
//+
    public function show(Tag $tag)
    {
      
//N+1問題はおきない。多対多のリレーション $tag->posts $tagで繰り返しがない。
        return view('tags.show',compact('tag'));
    }
```
blade

```php5:resources\views\tags\show.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ $tag->name }}</div>
                    <div class="card-body">
                        <x-flashMessage />

                       <x-posts.index :posts="$tag->posts"/>

                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```

:::note info
投稿でタグを追加して、投稿一覧が取得できるか確認する。
コメント数も取得できているか確認する。
:::

テキスト検索機能を実装する
---
```php5:routes\web.php

    Route::get('/', [App\Http\Controllers\PostController::class, 'index'])->name('posts.index');
    Route::resource('/posts', App\Http\Controllers\PostController::class, ['except' => 'index']);
    Route::resource('/comments', App\Http\Controllers\CommentController::class);
    Route::resource('/users', App\Http\Controllers\UserController::class);
    Route::resource('/categories', App\Http\Controllers\CategoryController::class);
    Route::resource('/tags', App\Http\Controllers\TagController::class);

    //+
    Route::get('/search', [App\Http\Controllers\PostController::class, 'search'])->name('search');
```

ポストのタイトル、コンテンツ、
コメントのコンテンツを
検索する。
ナビバーに検索フォームを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/cad45727-64db-0bde-2177-ef9cde589d52.png)

```php5:resources\views\layouts\app.blade.php
<!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
<!-- + -->
                        <form class="form-inline mr-3" action="{{ route('search') }}" method="get">
                            <input class="form-control mr-sm-2" type="search" name="text_search">
                            <button type="submit" class="btn btn-outline-success my-2 my-sm-0">検索</button>
                          </form>
                        <!-- Authentication Links -->
                        @guest
```
https://qiita.com/take_3/items/1154ecbd8033a9a3beaf

```php5:app\Models\Post.php
//+
    // 部分一致検索
    public function scopeWhereLike($query, string $column, string $keyword)
    {
        return $query->where($column, 'like', '%' . addcslashes($keyword, '%_\\') . '%');
    }

    // 前方一致検索
    public function scopeWhereLikeForward($query, string $column, string $keyword)
    {
        return $query->where($column, 'like', addcslashes($keyword, '%_\\') . '%');
    }
```
<details><summary>お前らのLike検索は間違っている</summary><div>
記事みたいにトレイットで実装する場合。
モデルで共通して使える関数とかはトレイットで書いたほうがいい。

```
php artisan make:trait TestTrait
できないっていうかない。
```
traitファイルは自力作成したほうが早そう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/a4183038-88dc-f4e3-e001-8450748603ae.png)

```windows
type nul > app\Models\Traits\WhereLike.php
```
空のファイルが作成されるので、

namespaceを忘れずに、
　　相対パスが、app\Models\Traits\WhereLike.phpなら、
　　namespaceは、App\Models\Traits;
trait 名前{　ここにスコープ関数をかくだけ。　}

```php5:app\Models\Traits\WhereLike.php
<?php
namespace App\Models\Traits;

trait WhereLike
{
    // 部分一致検索
    public function scopeWhereLike($query, string $column, string $keyword)
    {
        return $query->where($column, 'like', '%' . addcslashes($keyword, '%_\\') . '%');
    }

    // 前方一致検索
    public function scopeWhereLikeForward($query, string $column, string $keyword)
    {
        return $query->where($column, 'like', addcslashes($keyword, '%_\\') . '%');
    }
}
```
あとは、使いたいモデルでuseするだけ。

```php5:app\Models\Post.php
//+
   use App\Models\Traits\WhereLike;

   class Post extends Model
  {
//+
      use HasFactory,WhereLike;
```
以上でWhereLikeの関数がモデルで使えるようになる。

```php5:コントローラー
Post::whereLike('name','山田')->get()
```
</div></details>

```php5:app\Http\Controllers\PostController.php
    public function search(Request $request)
    {
        $keywords = $request->text_search ?? '';
        $posts = Post::whereLike('content', $keywords)
            ->orWhere(function ($q) use ($keywords) {
                 $q->whereLike('title', $keywords);
            })
//コメントも含めて検索する場合
/* 使用する場合、traitをCommentモデルにも追加する必要がある。         
            ->orWhere(function ($q) use ($keywords) {
                $comments_ids = Comment::whereLike('content', $keywords)->pluck('post_id');
                 $q->whereIn('id', $comments_ids);
            })
*/
//)->withQueryString()検索クエリを持ち越してくれる。
            ->paginate(3)->withQueryString();
//dd($request->text_search);
//取得したposts一覧をposts.indexで表示してやる。
        return view('posts.index', compact('posts', 'keywords'));
    }
```
`->withQueryString()`がない場合。

１ページ目のuriは ?text_search=''とクエリがある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/99d10500-5d71-e511-672f-993cd1666fb3.png)
ページネーションも7件。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/6617151f-8b43-651b-99dd-92af376f63d8.png)
２ページ目をクリックすると、uriからクエリがクリアーされている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/9abcabd8-235e-8321-1714-f44e5882366e.png)
ページネーションもクリアーされて、絞り込みも解除されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/4eb36f9a-fb58-18af-6e13-f143721e8f5a.png)
->withQueryString()がある場合。
２ページ以降もクエリーがクリアーされないで、繰り越してくれる。
クエリー文字列は dd($request->text_search);で取得できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/405bea12-3cc2-6fcc-da21-306d262eb7b3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1830335/55f5ff8f-2705-6250-8e53-6291dcb1e4b8.png)
検索キーワードと取得件数を表示させる。

```html:resources\views\posts\index.blade.php
{{-- + cardの上にコンポーネントを追記 --}}
                @isset($keywords)
                <x-search_result :paginator="$posts" :keywords="$keywords"/>
                @endisset
                
                <x-flashMessage/>
                <div class="card">
```
コンポーネントの作成
・search_result.blade.php

```html:resources\views\components\search_result.blade.php

{{-- 公式サイト ページネーション https://readouble.com/laravel/8.x/ja/pagination.html --}}

{{-- bootstrap4のalertをコピペ編集  --}}
<div id="search_msg" class="alert alert-warning alert-dismissible fade show" role="alert">
       <p class="mb-0 py-1"><strong>検索キーワード：</strong>{{ $keywords }}</p>

{{-- $posts を $paginator に変更しています。component受け渡し時 --}}
        <p class="mb-0 py-1"><strong>総件数：</strong>{{ $paginator->total() }} 件</p>
        <button type="button" class="close" data-dismiss="alert" aria-label="閉じる">
            <span aria-hidden="true">&times;</span>
        </button>
</div>

@push('js')
    <script>
// alertbuttonが閉じたら、route('posts.index')に遷移
        $('#search_msg').on('close.bs.alert', function() {
            window.location.href = "{{ route('posts.index') }}"
        })
    </script>
@endpush
```
スペースで、AND検索を実装する。

https://note.com/kakidamediary/n/nea3a9fc364d4

```php5:app\Http\Controllers\PostController.php
    public function search(Request $request)
    {
// 空の時はリターンさせる。
        if (is_null($request->text_search)) {
            return redirect()-> route('posts.index');
        }

// 検索文字列全体の前後にある空白を除去
        $keywords = preg_replace( '/\A[\p{C}\p{Z}]++|[\p{C}\p{Z}]++\z/u', '', $request->text_search);

// 1、空白を半角空白に統一する
        $keywords = mb_convert_kana($keywords, 's');

// 2、半角空白で文字を区切り、配列にする。
        $keywords_array = preg_split('/[\s]+/', $keywords);

// 3、配列をforeachで回し、それぞれ (WhereLike + orWhere) +(and)+  (WhereLike + orWhere)+(and)+・・・・
        foreach($keywords_array as $keyword){

            $posts = Post::whereLike('content', $keyword)

            ->orWhere(function ($q) use ($keyword) {
                $q->whereLike('title', $keyword);
            })
//コメントのcontent検索
            /*
            ->orWhere(function ($q) use ($keywords) {
                $comments_ids = Comment::whereLike('content', $keywords)->pluck('post_id');
                 $q->whereIn('id', $comments_ids);
            })
            */
            ->paginate(3)->withQueryString();
        }
        return view('posts.index', compact('posts', 'keywords'));
    }
```