作成物
この記事では、「キーワード検索」+「プルダウンより選択形式の検索」による複数条件での検索機能を持つ教材管理アプリを作成します。
テーブル構成
■教材テーブル
・教材ID
・教材名
・媒体ID
・カテゴリーID
■媒体テーブル
・媒体ID
・媒体
■カテゴリーテーブル
・カテゴリーID
・カテゴリー
手順
- マイグレーションファイルの作成
- サンプルデータの挿入(シーダー)
- モデルの作成
- ビューファイルの作成
- コントローラーの作成
- ルーティングの設定
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テーブルのマイグレーションファイルよりも先でなければいけません。
class CreateMediaTable extends Migration
{
    public function up()
    {
        Schema::create('media', function (Blueprint $table) {
            $table->increments('id')->comment('媒体ID');
            $table->string('medium')->comment('媒体名(書籍/動画/etc...)');
        });    }
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...)');
        });
    }
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
テーブルを作成することができました。
2. サンプルデータの挿入(シーダー)
シーダーファイルの作成
$ php artisan make:seed TeachingMaterialsTableSeeder
$ php artisan make:seed MediaTableSeeder
$ php artisan make:seed CategoryTableSeeder
/アプリ名/database/seedersにシーダーファイルが作成されます。
シーダーファイルの編集
3つのシーダーファイルに、サンプルデータを記述していきます。
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'
        ]);
        // 略
}
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'
        ]);
    }
}
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を呼び出す順番は、参照元を先にしなければいけません。
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(MediaTableSeeder::class);
        $this->call(CategoriesTableSeeder::class);
        $this->call(TeachingMaterialsTableSeeder::class);
    }
}
シーダーの実行
$ php artisan db:seed
サンプルデータを作成することができました。
3. モデルの作成
媒体:教材 = 1:多
コンテンツ:教材 = 1:多
の関係になります。
各モデルにリレーションを書いていきます。
class TeachingMaterial extends Model
{
    use HasFactory;
    public function medium()
    {
        return $this->belongsTo(Medium::class);
    }
    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}
class Medium extends Model
{
    use HasFactory;
    public function teaching_material()
    {
        return $this->hasMany(TeachingMaterial::class);
    }
}
class Category extends Model
{
    use HasFactory;
    public function teaching_material()
    {
        return $this->hasMany(TeachingMaterial::class);
    }
}
4. ビューファイルの作成
今回はbladeの機能は使わず、簡易的に1つのビューファイルに記述します。
検索機能は3つです。
・キーワードを入力し、教材名から部分一致検索
・媒体をプルダウンより選択して検索
・カテゴリーをプルダウンより選択して検索
プルダウンの選択肢には、データベースに保存されている値を呼び出しています。
<!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でテーブル結合します。
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. ルーティングの設定
Route::get('/', [TeachingMaterialController::class, 'index'])->name('index');
以上です^^



