Webアプリ開発　記事の詳細とコメント編

# 初めに
webアプリを開発したので、開発中に考えたことをまとめます
今回は記事の詳細とコメント機能についてです。

# 開発環境
macOS Sonoma 14.4.1
CentoOS Stream X_86_64
Apache/2.4.57
PHP 8.3.6
mysql Ver 8.0.36
phpMyAdmin 5.2.1
composer version 2.7.2
Laravel Installer 5.7.1
Laravel Framework 11.0.5

# ソースコード

https://github.com/opatu07/portfolio

# 考えたこと
## 記事の詳細
### 実装事例
ホーム画面から

![layout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/884220d0-500b-7525-0c0c-652b7afef065.png)

記事のタイトルまたは「この記事を読む」を押してもらうと記事の詳細を確認できます。
今回の例では「スープを作った」が記事のタイトルです。


![detailArticle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/fcd46a0e-4051-3813-8b44-a053c35c7256.png)

記事の詳細画面ではタイトル、サムネイル画像、本文、投稿日、コメントが確認できます。

### ビュー

```posts.index.blade.php
<x-layout>
    @include ('posts._header')
    <main class="max-w-6xl mx-auto mt-6 lg:mt-20 space-y-6 pb-10">
        @if ($posts->count())
            <x-posts-grid :posts="$posts" />

            {{ $posts->links() }}
        @else
            <p class="text-center">投稿はありません。しばらく経ってからお越しください。</p>
        @endif
    </main>
</x-layout>
```
これがホーム画面に記事を表示する大枠を決めるファイルです。
@includeでタイトル検索やカテゴリー検索のファイルを読み込んでいます。ifで記事があるかどうかの確認をしています。なければテキストを表示するようにしました。

```posts-grid.blade.php
@props(['posts'])

<x-post-featured-card :post="$posts[0]" />

@if ($posts->count() > 1)
    @foreach ($posts->skip(1) as $post)
        <x-post-card
            :post="$post"
            class="{{ $loop->iteration < 3 ? 'col-span-3' : 'col-span-2' }}"
        />
    @endforeach
@endif

```

posts-grid.blade.phpでは投稿したい記事の番号ごとにファイルへ情報を振り分けます
featured-cardでは最新の記事をpost-cardでは過去の記事を表示するようにします。

記事の詳細の表示は
```show.blade.php
<x-layout>
    <main class="max-w-6xl mx-auto mt-10 lg:mt-20 space-y-6">
        <article class="max-w-4xl mx-auto lg:grid lg:grid-cols-12 gap-x-10">
            <div class="col-span-4 lg:text-center lg:pt-14 mb-10">
               
                <img src="{{ asset('storage/' . $post->thumbnail) }}" alt="" class="rounded-xl" width="600" height="150"> 

                <div class="flex items-center lg:justify-center text-sm mt-4">
                    <div class="ml-3 text-left">
                        <h5 class="font-bold">
                            <a href="/?author={{ $post->author->username }}">{{ $post->author->name }}</a>
                        </h5>
                    </div>
                </div>
            </div>

            <div class="col-span-8">
                <div class="hidden flex justify-between mb-6">
                    <a href="/"
                       class="transition-colors duration-300 relative inline-flex items-center text-lg hover:text-orange-500">
                        <svg width="22" height="22" viewBox="0 0 22 22" class="mr-2">
                            <g fill="none" fill-rule="evenodd">
                                <path stroke="#000" stroke-opacity=".012" stroke-width=".5" d="M21 1v20.16H.84V1z">
                                </path>
                                <path class="fill-current"
                                      d="M13.854 7.224l-3.847 3.856 3.847 3.856-1.184 1.184-5.04-5.04 5.04-5.04z">
                                </path>
                            </g>
                        </svg>

                        <div>投稿一覧へ戻る</div>
                    </a>

                    <div class="space-x-2">
                        <x-category-button :category="$post->category"/>
                    </div>
                </div>

                <h1 class="font-bold text-3xl lg:text-4xl mb-10">
                    {{ $post->title }}
                </h1>

                <div class="space-y-4 lg:text-lg leading-loose">{!! nl2br(htmlspecialchars($post->body)) !!}</div>
            </div>
            <p class="mt-4 block text-gray-400 text-xs">
                投稿日
                <time>{{ $post->created_at->diffForHumans() }}</time>
            </p>
        </article>
        <section class="col-span-8 col-start-5 mt-10 space-y-6">

                @foreach ($post->comments as $comment)
                    <x-post-comment :comment="$comment"/>
                @endforeach

                @include ('posts._add-comment-form')

        </section>
    </main>
</x-layout>

```
このファイルで行います

### コントローラ

```PostController.php

<?php

namespace App\Http\Controllers;
use App\Models\Post;

class PostController extends Controller
{   
    public function index()
    {
        return view('posts.index', [
            'posts' => Post::latest()->filter(
                        request(['search', 'category', 'author'])
                    )->paginate(7)->withQueryString()
            ]);
        
        
    }
    
    public function show(Post $post)
    {   
        return view('posts.show', [
            'post' => $post
        ]);
    }

}
```
投稿記事を最新のものを表示するようにしました。

### ルート
```
Route::get('posts/{post:slug}', [PostController::class, 'show']);
```
### レスポンシブ対応

使われるスマホがiPhone8以上の画面サイズなのでiPhone8に対応させました。
![res_detail_article.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/018ea6c0-7a16-e86f-4bcb-0e86bdc60280.gif)



## コメント機能

### 実装事例

![detailArticle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/fcd46a0e-4051-3813-8b44-a053c35c7256.png)

新規登録かログインをしないとコメントはできません

![comment.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/e587c7e1-623c-2075-d6a7-6cd97a834ea2.png)

新規登録かログイン後に記事の詳細を見るとコメントフォームが表示されるようになります。

コメント後は
![showComment.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/6df8f746-12b5-1f5b-8cee-2f866d3438cd.png)
コメントが表示されていることが確認できます。


### ビュー
```posts.show.blade.php
<section class="col-span-8 col-start-5 mt-10 space-y-6">

    @foreach ($post->comments as $comment)
        <x-post-comment :comment="$comment"/>
    @endforeach
    
    @include ('posts._add-comment-form')
</section>
```
記事の詳細表示画面にコメント関連のファイルを読み込ませました

```_add-comment-form.blade.php
@auth
<section class="px-6 py-8">
    <main class="max-w-lg mx-auto mt-10">
        <x-panel>
            <h1 class="text-center font-bold text-xl">コメントする</h1>
            <form method="POST" action="/posts/{{ $post->slug }}/comments">
                @csrf

                <div class="mt-6">
                    <textarea name="body" class="w-full text-sm outline outline-gray-200 focus:outline-orange-300" rows="5" placeholder="自由にコメントしてください。" required></textarea>

                    @error('body')
                    <span class="text-xs text-red-500">{{ $message }}</span>
                    @enderror
                </div>

                <div class="flex justify-end mt-6 pt-6 border-t border-gray-200">
                    <x-form.button>コメントする</x-form.button>
                </div>
            </form>
        </x-panel>
    </main>
</section>
@else
<p class="font-semibold text-center mb-8">
    この記事にコメントするには<a href="/register" class="hover:underline">ユーザー登録</a> か
    <a href="/login" class="hover:underline">ログイン</a>してください。
</p>
@endauth
```
コメントフォームの表示では
authファザードでコメントできるユーザーの判別を行なっています。
フォームはコンポーネントファイルにあるものを使いました。
authファザードを外れたユーザーはユーザー登録かログインをしてもらうようにリンク付きのテキストを表示しました。

```post-comment.blade.php
@props(['comment'])

<x-panel class="bg-gray-50">
    <article class="flex space-x-4">
        <div>
            <header class="mb-4">
                <h3 class="font-bold">{{ $comment->author->username }}</h3>

                <p class="text-xs">
                    投稿日
                    <time>{{ $comment->created_at->diffForHumans() }}</time>
                </p>
            </header>

            <p>
                {{ $comment->body }}
            </p>
        </div>
    </article>
</x-panel>
```
このファイルでユーザーネーム、コメント、投稿日を表示できるようにしました。

コメント機能は表示と入力があるので、それぞれの機能ごとにファイルを分けて修正がしやすいようにしました。

### コントローラ

```PostsCommentsController.php
<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostCommentsController extends Controller
{
    public function store(Post $post)
    {
        request()->validate([
            'body' => 'required|max:50'
        ]);

        $post->comments()->create([
            'user_id' => request()->user()->id,
            'body' => request('body')
        ]);

        return back();
    }
}

```
コメントの長さは調整したかったのでバリデーションを行いました。

### レスポンシブ対応
使われるスマホがiPhone8以上の画面サイズなのでiPhone8に対応させました。
![res_comment.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/4831ba57-aecf-7414-9054-55151c545d52.gif)


# 改善したいこと

## ホーム画面の修正
ホーム画面では最新の投稿記事が一番上に表示されます。
ですが「最新記事」みたいな表示がないとユーザーがわからないと思うのでなんらかの表示を追加したいと思います。
また記事の詳細に飛びやすくするために「記事を読む」ボタンを削除し、記事の枠をクリックされたら記事の詳細を読めるように変更したいです。

## 記事詳細画面の修正
パソコンなどの画面が大きなデバイスでは「投稿一覧へ戻る」ボタンがいると思うので
レスポンシブ対応でつけたhiddenに追加してlg:inline-blockを追加したいと思います。
また記事詳細画面ではカテゴリーボタンはいらないと思うので削除したいと思います。
最後に記事作成者を表示するようになっているのでそこも削除したいと思います。

## コメントフォームのコンポーネント化

コメントフォーム内のラベル、<textarea>、エラーはコンポーネント化できると思うのでやります。


# 最後に
レスポンシブ対応がうまくできてなかったことが発見できてよかったです。
機能修正していきます。