##作成物
この記事では、「キーワード検索」+「プルダウンより選択形式の検索」による複数条件での検索機能を持つ教材管理アプリを作成します。
##テーブル構成
■教材テーブル
・教材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');
以上です^^