8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-09-12

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

スクリーンショット 2021-09-12 18.00.03.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」に注意。

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

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

よってここでは、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

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

##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)から呼び出します。

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

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

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

##3. モデルの作成

媒体:教材 = 1:多
コンテンツ:教材 = 1:多
の関係になります。

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

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つです。
・キーワードを入力し、教材名から部分一致検索
・媒体をプルダウンより選択して検索
・カテゴリーをプルダウンより選択して検索

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

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');

以上です^^
8
11
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?