laravelを使用して商品検索機能を作成しました。
自分の理解のために記事にさせて頂きます。
要件定義
クエリビルダを用いて、以下の4通りのパターンの検索機能の実装方法を解説。
1.何も入力せずに全件表示
2.商品名を入力して検索
3.カテゴリを選択して表示
4.商品名を入力かつカテゴリを選択して絞り込む(and検索)
一つ一つ説明させて頂きます。
4.商品名を入力かつカテゴリを選択して絞り込む(and検索)
#マイグレーションファイルとシーダーを作成
今回必要なデータベースは
・m_products_tableテーブルのid、product_name、category_id、priceと、
・m_categoriesテーブルのid、category_nameです。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('m_products', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('product_name', 64)->comment('商品名');
$table->bigInteger('category_id')->unsigned()->comment('カテゴリーID');
$table->integer('price')->comment('販売単価');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('m_products');
}
}
<?php
use Illuminate\Database\Seeder;
class ProductsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('m_products')->insert([
[
'id' => 1,
'product_name' => 'マンゴー',
'category_id' => 1,
'price' => 800,
'description' => '沖縄の美味しいマンゴーです。',
'sale_status_id' => 1,
'product_status_id' => 1,
'regist_date' => new DateTime,
'user_id' => 1
],
[
'id' => 2,
'product_name' => 'ワンピース',
'category_id' => 2,
'price' => 1000,
'description' => '限定のワンピースです。',
'sale_status_id' => 2,
'product_status_id' => 2,
'regist_date' => new DateTime,
'user_id' => 2
],
[
'id' => 3,
'product_name' => 'Tシャツ',
'category_id' => 2,
'price' => 800,
'description' => '涼しげなTシャツです',
'sale_status_id' => 2,
'product_status_id' => 2,
'regist_date' => new DateTime,
'user_id' => 3
],
[
'id' => 4,
'product_name' => '南国バナナ',
'category_id' => 1,
'price' => 500,
'description' => '南国の美味しいバナナです。',
'sale_status_id' => 2,
'product_status_id' => 2,
'regist_date' => new DateTime,
'user_id' => 2
],
[
'id' => 5,
'product_name' => 'パジャマ',
'category_id' => 2,
'price' => 1200,
'description' => '寝心地の良いパジャマです',
'sale_status_id' => 2,
'product_status_id' => 2,
'regist_date' => new DateTime,
'user_id' => 1
],
[
'id' => 6,
'product_name' => '青森のりんご',
'category_id' => 1,
'price' => 1000,
'description' => '甘い青森のりんごです',
'sale_status_id' => 2,
'product_status_id' => 3,
'regist_date' => new DateTime,
'user_id' => 2
],
[
'id' => 7,
'product_name' => 'ジーンズ',
'category_id' => 2,
'price' => 1500,
'description' => '何にでも合わせやすいジーンズです',
'sale_status_id' => 1,
'product_status_id' => 1,
'regist_date' => new DateTime,
'user_id' => 3
],
[
'id' => 8,
'product_name' => 'クッション',
'category_id' => 3,
'price' => 700,
'description' => 'フカフカのクッションです',
'sale_status_id' => 1,
'product_status_id' => 1,
'regist_date' => new DateTime,
'user_id' => 1
],
[
'id' => 9,
'product_name' => 'カーペット',
'category_id' => 3,
'price' => 3000,
'description' => '季節問わず使用可能のカーペットです',
'sale_status_id' => 1,
'product_status_id' => 1,
'regist_date' => new DateTime,
'user_id' => 2
]
}
}
カテゴリーのテーブルとシーダーを作成
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMCategoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('m_categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('category_name')->comment('カテゴリー名');
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('m_categories');
}
}
<?php
use Illuminate\Database\Seeder;
class CategoriesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('m_categories')->insert([
[
'id' => 1,
'category_name' => '食品'
],
[
'id' => 2,
'category_name' => '衣類'
],
[
'id' => 3,
'category_name' => '雑貨'
]
]);
}
}
#モデルの作成
次にモデルを作成します。「カテゴリは複数の商品を持つ(hasMany)」、「商品はカテゴリに属する(belongsTo)」というリレーション関係を作りたいので、以下のように記述します。
<?php
namespace App;
use App\Category;
use App\OrderDetail;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $table = 'm_products';
public function category()
{
return $this->belongsTo(Category::class);
}
public function orderDetail()
{
return $this->hasMany(OrderDetail::class);
}
}
<?php
namespace App;
use App\Product;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $table = 'm_categories';
public static function getLists()
{
$categories = Category::pluck('category_name', 'id');
return $categories;
}
public function products()
{
return $this->hasMany(Product::class);
}
}
protected $table = 'm_products'; は、プロジェクトの関係上
マイグレーションファイルの単数形で名前をつけていないため、連携されていないので、m_productsを連携するようにしています。
protected $table = 'm_categories';も同様で、m_categoriesを連携させる
ために記入をしています。
通常はモデルのファイル名を命名規則通りに付けることで、テーブルと勝手に接続してくれます。
Category.phpの
$categories = Category::pluck('category_name', 'id');
return $categories;
の部分では、m_categoriesのテーブルの中からcategory_name,とidを
取り出して、$categoriesに入れてreturnで返しています。
#商品検索画面へのルーティングを作成
Route::group(['middleware' => 'auth'], function () {
Route::prefix('products')->group(function (){
Route::get('/', 'ProductsController@search')->name('search.products');
Route::get('/{id}', 'ProductsController@showDetail')->name('search.detail');
});
Route::group(['middleware' => 'auth']
は、ユーザー登録をしているユーザーのみ開けるよう設定しまとめています。
Route::prefix('products')->group
prefixとは、web.phpでRouteを設定するとき、URLの接頭(プレフィックス)をグループ化して設定できる便利なメソッドです。
prefixを使用しない場合
Route::get('products/', 'ProductsController@search')->name('search.products');
Route::get('products/{id}', 'ProductsController@showDetail')->name('search.detail');
と、URLの先頭部をわざわざ二つ書かなくてはならない形になります。
今回は2つのみの記載のため、prefixを使用せずとも問題はありませんが、
もし同じURL先頭部でのRouteが多くなってしまう場合はprefixを使用した方が楽かと思います。
#コントローラーの作成
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Product;
use App\Category;
class ProductsController extends Controller
{
public function search(Request $request)
{
$query = Product::query(); //Productのデーターベースを参照 (queryは参照)
$categories = Category::getLists();
$searchWord = $request->input('product_name');
$categoryId = $request->input('category_id');
if (isset($searchWord)) {
$query->where('product_name', 'like', '%' . $searchWord . '%'); // %を二つ書く事で曖昧検索
}
if (isset($categoryId)) {
$query->where('category_id', $categoryId);
}
$products = $query->orderBy('category_id', 'asc')->paginate(15); //15件まで出す
return view('products.search', [
'products' => $products,
'categories' => $categories,
'searchWord' => $searchWord,
'categoryId' => $categoryId,
]);
}
public function showDetail($id)
{
$product = Product::findOrFail($id);
return view('products.detail_a_product',compact('product'));
}
}
まず、
public function search(Request $request)
{
$query = Product::query();
$categories = Category::getLists();
$searchWord = $request->input('product_name');
$categoryId = $request->input('category_id');
}
実際に検索する際の処理を書いていきます。
search(検索)という関数を作り、中に処理を書きます。
$query = Product::query();
$categories = Category::getLists();
は共にProductとCategoryのデーターベースを参照しています。
Productのテーブルからデーターを読み込んで下さいね。
Categoryのテーブルからデーターを読み込んで下さいね。
これをそれぞれ変数に入れておきます。
$searchWord = $request->input('product_name');
$categoryId = $request->input('category_id');
リクエストされた(ユーザーが入力した)product_nameのデーターを取得します。
同じく、
リクエストされた(ユーザーが入力した)category_idのデーターを取得します。
これもそれぞれの変数に入れます。
if (isset($searchWord)) {
$query->where('product_name', 'like', '%' . $searchWord . '%');
}
if (isset($categoryId)) {
$query->where('category_id', $categoryId);
}
もし、変数の$searchWordがセットされていたら
Productのテーブルからデーターを読み込んで下さいね。と指定しておいた$queryの
中からwhereで特定の条件を満たすデータを取り出します。
ここでは$searchWord(リクエストされた(ユーザーが入力した)product_nameのデーター)を
product_nameで探してくださいという処理をしています。
また、%を2ついれる事により曖昧検索を可能にしています。
(例えばバナナを「バナ」「バ」「ナ」と一部を入力していれば検索に引っかかるようにしている)
下の$categoryIdも同じ要領です。カテゴリーIDには曖昧検索は必要ありませんね。
$products = $query->orderBy('category_id', 'asc')->paginate(15);
orderByを使用してカテゴリーのIDを数字の順番に並べ、15件まで表示される
ようになっている。15件以上は次のページになる
return view('products.search', [
'products' => $products,
'categories' => $categories,
'searchWord' => $searchWord,
'categoryId' => $categoryId,
]);
returnでviewファイルのproductsのsearchに
'products' => $products,
'categories' => $categories,
'searchWord' => $searchWord,
'categoryId' => $categoryId,
を渡します。returnしておく事で、viewで変数を使用できるようになり、
表示できるようになります。
public function showDetail($id)
{
$product = Product::findOrFail($id);
return view('products.detail_a_product',compact('product'));
}
Product::findOrFail($id) でProductのテーブルからidを
#Viewファイル説明
@extends('layouts.app')
@section('content')
<div>
<div class="row mt-4">
<div class="col-lg-12 text-center">
<h1>商品検索画面</h1>
</div>
</div>
<form method="get" action="{{ route('search.products') }}">
@csrf
<div class="row">
<div class="input-group mt-4 col-md-7 offset-2">
<h2 class="mr-4">商品名</h2>
<input type="text" class="form-control" name="product_name">
<span class="input-group-btn">
<button type="submit" class="btn btn-primary ml-4">検索</button>
</span>
</div>
</div>
<div class="row">
<div class="input-group mt-4 col-md-7 offset-2">
<h2>商品カテゴリ</h2>
<select name="category_id" class="ml-2" style="width:50%; text-align-last:center;">
<option value="">未選択</option>
@foreach($categories as $categoryid => $categoryName)
<option value="{{ $categoryid }}">
{{ $categoryName }}
</option>
@endforeach
</select>
</div>
</div>
</form>
@if (!empty($products))
<p>全{{ $products->count() }}件</p>
<table border="1" class="table" style="border-collapse: collapse">
<thead class="bg-warning">
<tr>
<th>商品名</th>
<th>商品カテゴリ</th>
<th>価格</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($products as $product)
<tr>
<td>{{ $product->product_name }}</td>
<td>{{ $product->category->category_name }}</td>
<td>{{ $product->price }}円</td>
<td name="name"><a href="{{ route('search.detail',$product->id) }}" class="btn btn-primary">商品詳細</a></td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
@endsection
###GETメソッドとルーティング
viewファイルを中心に解説していきます。
検索フォームのformタグには
とありますが、これは「検索ボタンがクリックされたとき、GETメソッドを実行し、コントローラーのsearch.productsを呼び出す」ことを意味しています。#商品一覧テーブル
検索が実行されたときにテーブルを表示する
テーブルを@if (!empty($products))で囲います。
表示件数を表示
検索結果を表示したい場合、count()関数を用います。今回は該当する商品($products)の数を表示したいので、以下のように記述します。
全{{ $products->count() }}件
テーブルをforeachで作成
表示したい情報は、「商品名」「商品カテゴリ」「価格」「商品詳細(遷移画面は作りません)」の4つです。
そのためforeachで以下のように記述しています。
@foreach($products as $product)
<tr>
<td>{{ $product->product_name }}</td>
<td>{{ $product->category->category_name }}</td>
<td>{{ $product->price }}円</td>
<td name="name"><a href="{{ route('search.detail',$product->id) }}" class="btn btn-primary">商品詳細</a></td>
</tr>
@endforeach



