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

# 考えたこと
## カテゴリー検索
### 実装事例
![category.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/b34cbb01-92ba-3815-7a9b-1a60218fe7cf.png)

投稿記事一覧の下にあるカテゴリー検索を押すと投稿記事の一覧とカテゴリー別で記事を確認できる

### ビュー
```_header.blade.php

<header class="max-w-xl mx-auto md:mt-20 mt-10 text-center">
    <h1 class="md:text-4xl text-2xl">
        投稿記事一覧
    </h1>

    <div class="space-y-2 lg:space-y-0 lg:space-x-4 mt-4">
        <div class="relative inline-flex bg-gray-100 rounded-xl w-40">
            <x-category-dropdown />
        </div>

        <!-- Search -->
        <div class="relative inline-flex items-center bg-gray-100 rounded-xl px-3 w-44">
            <form method="GET" action="/">
                @if (request('category'))
                    <input type="hidden" name="category" value="{{ request('category') }}">
                @endif

                <input type="text"
                       name="search"
                       placeholder="タイトル検索"
                       class="bg-transparent placeholder-gray-500 font-semibold text-sm py-2"
                       value="{{ request('search') }}"
                >
            </form>
        </div>
    </div>
</header>

```

ホーム画面の上側はこのファイルで作成した。
カテゴリーのドロップダウンはコンポーネント化した。

```category-dropdown.blade.php
<x-dropdown>
    <x-slot name="trigger">
        <button class="py-2 pl-3 pr-9 text-sm text-gray-500 font-semibold w-full lg:w-40 text-center flex lg:inline-flex">
            {{ isset($currentCategory) ? ucwords($currentCategory->name) : 'カテゴリー検索' }}
        </button>
    </x-slot>

    <x-dropdown-item
        href="/?{{ http_build_query(request()->except('category', 'page')) }}"
        :active="request()->routeIs('home') && is_null(request()->getQueryString())"
    >
        All
    </x-dropdown-item>

    @foreach ($categories as $category)
        <x-dropdown-item
            href="/?category={{ $category->slug }}&{{ http_build_query(request()->except('category', 'page')) }}"
            :active='request()->fullUrlIs("*?category={$category->slug}*")'
        >
            {{ ucwords($category->name) }}
        </x-dropdown-item>
    @endforeach
</x-dropdown>

```
このwebアプリのドロップダウンはdropdownとdropdown-itemの2つのコンポーネントファイルで管理するようにしました。
カテゴリーもそのようにしました。

### コントローラ

```PostController.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()
            ]);
        
    }

}
```

requestメソッドでcatetoryのデータを抽出します。

### モデル
```Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $with = ['category', 'author'];

    public function scopeFilter($query, array $filters)
    {
        $query->when($filters['search'] ?? false, fn($query, $search) =>
            $query->where(fn($query) =>
                $query->where('title', 'like', '%' . $search . '%')
                    ->orWhere('body', 'like', '%' . $search . '%')
            )
        );

        $query->when($filters['category'] ?? false, fn($query, $category) =>
            $query->whereHas('category', fn ($query) =>
                $query->where('slug', $category)
            )
        );

        $query->when($filters['author'] ?? false, fn($query, $author) =>
            $query->whereHas('author', fn ($query) =>
                $query->where('username', $author)
            )
        );
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    public function author()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}
```
PostモデルではscopeFilterメソッドでタイトルとカテゴリーの検索をしています。

## タイトル検索
### 実装事例

![titleSearch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/29496d94-c51c-cd3c-46d7-0839767426e1.png)

タイトル検索にタイトルを入力するとタイトルの記事が表示されます。

### ビュー

```_header.blade.php
<header class="max-w-xl mx-auto md:mt-20 mt-10 text-center">
    <h1 class="md:text-4xl text-2xl">
        投稿記事一覧
    </h1>

    <div class="space-y-2 lg:space-y-0 lg:space-x-4 mt-4">
        <div class="relative inline-flex bg-gray-100 rounded-xl w-40">
            <x-category-dropdown />
        </div>

        <!-- Search -->
        <div class="relative inline-flex items-center bg-gray-100 rounded-xl px-3 w-44">
            <form method="GET" action="/">
                @if (request('category'))
                    <input type="hidden" name="category" value="{{ request('category') }}">
                @endif

                <input type="text"
                       name="search"
                       placeholder="タイトル検索"
                       class="bg-transparent placeholder-gray-500 font-semibold text-sm py-2"
                       value="{{ request('search') }}"
                >
            </form>
        </div>
    </div>
</header>
```
タイトル検索はこんな感じでビューを書きました。

### コントローラ

```PostController.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()
            ]);
        
    }

}
```
request(['search'])でタイトル検索をしています。

### モデル

```Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $with = ['category', 'author'];

    public function scopeFilter($query, array $filters)
    {
        $query->when($filters['search'] ?? false, fn($query, $search) =>
            $query->where(fn($query) =>
                $query->where('title', 'like', '%' . $search . '%')
                    ->orWhere('body', 'like', '%' . $search . '%')
            )
        );

    }

}
```
scopeFilterメソッドでタイトルを検索しています。



# 変更箇所

## _header.blade.phpの改善

```_header.blade.php
<input type="text"
    name="search"
    placeholder="タイトル検索"
    class="bg-transparent placeholder-gray-500 font-semibold text-sm py-2"
    value="{{ request('search') }}"
>
```
_header.blade.phpのタイトル検索のフォームのinputをコンポーネント化したいと思います

## カテゴリーの項目を変更する
投稿一覧だけAllになっているので日本語に変更したいと思います。

```
<x-dropdown>

    <x-dropdown-item
        href="/?{{ http_build_query(request()->except('category', 'page')) }}"
        :active="request()->routeIs('home') && is_null(request()->getQueryString())"
    >
        All
    </x-dropdown-item>

</x-dropdown>
```
# 最後に
変更箇所がカテゴリー、タイトル検索は少ないので新規機能追加や保守点検に時間を回したいです。