【Laravel】検索機能の実装②　~複数条件＆プルダウンからの選択形式~

##作成物
この記事では、「キーワード検索」＋「プルダウンより選択形式の検索」による複数条件での検索機能を持つ教材管理アプリを作成します。

![スクリーンショット 2021-09-12 18.00.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1208065/8179c549-5ccd-c0f3-dcf1-546ff903000d.png)


##テーブル構成

**■教材テーブル**
・教材ID
・教材名
・媒体ID
・カテゴリーID

**■媒体テーブル**
・媒体ID
・媒体

**■カテゴリーテーブル**
・カテゴリーID
・カテゴリー


#手順
1. マイグレーションファイルの作成
2. サンプルデータの挿入（シーダー）
3. モデルの作成
4. ビューファイルの作成
5. コントローラーの作成
6. ルーティングの設定

##1. マイグレーションファイルの作成
###テーブル作成
教材テーブル・媒体テーブル・コンテンツテーブルの3つを作成します。
※MySQLでデータベースを作成し、MySQLとの接続は済んでいるものとします。

```
$ php artisan make:migration create_TeachingMaterials_table
$ php artisan make:migration create_Media_table
$ php artisan make:migration create_Categories_table
```
※媒体：単数形「medium」 / 複数形「media」に注意。


###マイグレーションファイルの編集
各マイグレーションファイルのスキーマを構築します。

<font color="MediumBlue">外部キー参照先のマイグレーションファイルが実行されていない状態で、外部キーを設定したマイグレーションファイルが実行されると、参照するテーブルがないとエラーを返されます。</font>
マイグレーションはファイルを作成した順（ファイル名になっている作成日時順）に実行されます。

よってここでは、MediaテーブルとCategoriesテーブルのマイグレーションファイルの作成日時が、Teaching Materialsテーブルのマイグレーションファイルよりも先でなければいけません。

```2021_09_10_222418_create__media_table.php
class CreateMediaTable extends Migration
{
    public function up()
    {
        Schema::create('media', function (Blueprint $table) {
            $table->increments('id')->comment('媒体ID');
            $table->string('medium')->comment('媒体名（書籍/動画/etc...）');
        });    }
```

```2021_09_10_222418_create__categories_table.php
class CreateCategoriesTable extends Migration
{
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->increments('id')->comment('コンテンツID');
            $table->string('category')->comment('コンテンツ名（Laravel/Git/etc...）');
        });
    }
```

```2021_09_10_222419_create__teaching_materials_table.php
class CreateTeachingMaterialsTable extends Migration
{
    public function up()
    {
        Schema::create('teaching_materials', function (Blueprint $table) {
            $table->increments('id')->comment('教材ID');
            $table->string('name')->comment('教材名');
            $table->unsignedInteger('medium_id');
            $table->unsignedInteger('category_id');

            //外部キー制約の設定
            $table->foreign('medium_id')->references('id')->on('media')->comment('媒体ID');
            $table->foreign('category_id')->references('id')->on('categories')->comment('コンテンツID');
        });
    }
```

マイグレーションの実行

```
$ php artisan migrate
```

![スクリーンショット 2021-09-11 15.18.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1208065/7089e2fc-42ca-2140-5e75-c4bf15104c51.png)


テーブルを作成することができました。

##2. サンプルデータの挿入（シーダー）
###シーダーファイルの作成

```
$ php artisan make:seed TeachingMaterialsTableSeeder
$ php artisan make:seed MediaTableSeeder
$ php artisan make:seed CategoryTableSeeder
```
`/アプリ名/database/seeders`にシーダーファイルが作成されます。

###シーダーファイルの編集
3つのシーダーファイルに、サンプルデータを記述していきます。

```TeachingMaterialsTableSeeder.php
class TeachingMaterialsTableSeeder extends Seeder
{
    public function run()
    {
        \DB::table('teaching_materials')->insert([
        'id' => '1',
        'name' => 'Git： もう怖くないGit！チーム開発で必要なGitを完全マスター',
        'medium_id' => '1',
        'category_id' => '3'
        ]);

        \DB::table('teaching_materials')->insert([
        'id' => '2',
        'name' => 'PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門',
        'media_id' => '1',
        'category' => '4'
        ]);

        \DB::table('teaching_materials')->insert([
        'id' => '3',
        'name' => '最短・最速で学ぶFirebase Hosting + Vue Todoアプリ実装',
        'media_id' => '1',
        'category_id' => '7'
        ]);

        // 略

}
```

```MediaTableSeeder.php
class MediaTableSeeder extends Seeder
{
    public function run()
    {
        \DB::table('media')->insert([
        'id' => '1',
        'medium' => 'Udemy'
        ]);

        \DB::table('media')->insert([
        'id' => '2',
        'medium' => '書籍'
        ]);

        \DB::table('media')->insert([
        'id' => '3',
        'medium' => 'Techpit'
        ]);
    }
}
```

```CategoriesTableSeeder.php
class CategoriesTableSeeder extends Seeder
{
    public function run()
    {
        \DB::table('categories')->insert([
        'id' => '1',
        'category' => 'Laravel'
        ]);

        \DB::table('categories')->insert([
        'id' => '2',
        'category' => 'Web技術'
        ]);

        \DB::table('categories')->insert([
        'id' => '3',
        'category' => 'Git'
        ]);

        // 略
    }
}
```

###DatabaseSeeder.phpの編集
上記で作成したシーダーファイルを大元のシーダーファイル（DatabaseSeeder.php）から呼び出します。

<font color="MediumBlue">TeachingMaterialsTableにおいてMediaTablesとCategoriesTableを参照しているので、Seederを呼び出す順番は、参照元を先にしなければいけません。</font>

```DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(MediaTableSeeder::class);
        $this->call(CategoriesTableSeeder::class);
        $this->call(TeachingMaterialsTableSeeder::class);
    }
}
```

###シーダーの実行

```
$ php artisan db:seed
```

![スクリーンショット 2021-09-11 13.47.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1208065/1acb9ddd-a7d9-8a85-56c6-d4313815695e.png)


サンプルデータを作成することができました。

##3. モデルの作成

媒体：教材　＝　１：多
コンテンツ：教材　＝　１：多
の関係になります。

各モデルにリレーションを書いていきます。

```TeachingMaterial.php
class TeachingMaterial extends Model
{
    use HasFactory;

    public function medium()
    {
        return $this->belongsTo(Medium::class);
    }

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

```Medium.php
class Medium extends Model
{
    use HasFactory;

    public function teaching_material()
    {
        return $this->hasMany(TeachingMaterial::class);
    }
}
```

```Category.php
class Category extends Model
{
    use HasFactory;

    public function teaching_material()
    {
        return $this->hasMany(TeachingMaterial::class);
    }
}
```

##4. ビューファイルの作成
今回はbladeの機能は使わず、簡易的に1つのビューファイルに記述します。

検索機能は3つです。
・キーワードを入力し、教材名から部分一致検索
・媒体をプルダウンより選択して検索
・カテゴリーをプルダウンより選択して検索

プルダウンの選択肢には、データベースに保存されている値を呼び出しています。

```php:index.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Teaaching Materials</title>
    <link rel="stylesheet" href="{{ url('css/style.css') }}">
</head>
<body>

    //* 検索機能ここから *//
    <div class="search">
        <form action="{{ route('index') }}" method="GET">
            @csrf

            <div class="form-group">
                <div>
                    <label for="">キーワード
                    <div>
                        <input type="text" name="keyword" value="{{ $keyword }}">
                    </div>
                    </label>
                </div>

                <div>
                    <label for="">媒体
                    <div>
                        <select name="medium" data-toggle="select">
                            <option value="">全て</option>
                            @foreach ($media_list as $media_item)
                                <option value="{{ $media_item->getMedium() }}" @if($medium=='{{ $media_item->getMedium() }}') selected @endif>{{ $media_item->getMedium() }}</option>
                            @endforeach
                        </select>
                    </div>
                    </label>
                </div>

                <div>
                    <label for="">カテゴリー
                    <div>
                        <select name="category" data-toggle="select">
                            <option value="">全て</option>
                            @foreach ($categories_list as $categories_item)
                                <option value="{{ $categories_item->getCategory() }}" @if($category=='{{ $categories_item->getCategory() }}') selected @endif>{{ $categories_item->getCategory() }}</option>
                            @endforeach
                        </select>
                    </div>
                    </label>
                </div>

                <div>
                    <input type="submit" class="btn" value="検索">
                </div>
            </div>
        </form>
    </div>
    //* 検索機能ここまで *//

    <table>
        <tr>
            <th>教材名</th>
            <th>媒体</th>
            <th>コンテンツ</th>
        </tr>

        @foreach ($items as $item)
        <tr>
            <td>{{ $item->name }}</td>
            <td>{{ $item->medium }}</td>
            <td>{{ $item->category }}</td>
            //mediaテーブルとcategoriesテーブルを結合しているので、この記述でアクセスできる
        </tr>
        @endforeach
    </table>

</body>
</html>


```
##5. コントローラーの作成
リレーション先のテーブルのカラムも検索対象になっているので、joinでテーブル結合します。

```TeachingMaterialsController.php
class TeachingMaterialController extends Controller
{
    public function index(Request $request)
    {
        //検索フォームに入力された値を取得
        $medium = $request->input('medium');
        $category = $request->input('category');
        $keyword = $request->input('keyword');

        $query = TeachingMaterial::query();
        //テーブル結合
        $query->join('media', function ($query) use ($request) {
            $query->on('teaching_materials.medium_id', '=', 'media.id');
            })->join('categories', function ($query) use ($request) {
            $query->on('teaching_materials.category_id', '=', 'categories.id');
            });

        if(!empty($medium)) {
            $query->where('medium', 'LIKE', $medium);
        }

        if(!empty($category)) {
            $query->where('category', 'LIKE', $category);
        }

        if(!empty($keyword)) {
            $query->where('name', 'LIKE', "%{$keyword}%");
        }

        $items = $query->get();
        
        $media_list = Medium::all();
        $categories_list = Category::all();

        return view('index', compact('items', 'keyword', 'medium', 'category', 'media_list', 'categories_list'));
    }
}
```

##6. ルーティングの設定

```web.php
Route::get('/', [TeachingMaterialController::class, 'index'])->name('index');
```



<br>
以上です^^