21
23

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 1 year has passed since last update.

【Laravel】検索機能を実装する

Last updated at Posted at 2022-12-08

実装する検索機能一覧

このCRUDアプリでは検索結果をページネーション(分割表示機能)で
表示しています。
検索機能を実装するために必要な知識をまずはおさらいしますが
検索機能の実装方法についてすぐに確認したい場合は
次のリンク先にお進みください

  1. キーワード検索
  2. 上限値と下限値を設定しリレーション先を検索する

スクリーンショット 2022-12-03 21.40.46.png

Laravelで検索機能を実装するために必要な知識

Laravelには独自機能が存在し、少ないコードでSQLを実行したり、
多数の検索結果を分割して表示するページネーションと呼ばれる
技術があります

LaravelでDBを操作するコード

Eloquent(エロクエント)

データベースとLaravelで作成したモデルを対応づける機能です。

よって、EloquentでDBのレコードを取得するなどの操作する場合は
操作対象になるテーブル名を具体的に記述するのではなく
対応付けされたモデル名を記述していきます。

例として写真のようにproductsテーブルを作成した場合は
Laravelのプロジェクト内にはProducts.phpという
モデルを作成することになります
スクリーンショット 2022-12-02 10.45.18.png

products.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Companies extends Model
{
    Public function products()
  {
   
  }

Eloquentでのデータベースを操作する場合

データベースを操作するには元々、SQL文の知識が必要ですが
EloquentではSQL文の知識がなくてもデータベースを操作することができるため
初心者からプロまで幅広く利用できます

class Companies extends Model
{
    Public function products()
  {
       $products = Products::all(); 
        // productsテーブルの全てのレコードを取得します
       $任意の変数名 = モデル名::all();
        // モデル名は命名のルールとして頭文字が大文字になっています

       return view('user.index')->with('products', $products);

        // viewのファイルまでのパスは以下の設定です
        // プロジェクト¥resources¥views¥user¥index.blade.php

        // return view の引数にはビューが格納されているフォルダ名と
        // viewのファイル名を拡張子などを省略して記述します

        // withメソッドはビューへ変数の情報を個別で渡すことができます
        // 開くviewに対して -> withメソッドで変数情報を渡します。
        // withメソッドの引数は次のようになります
        // ('viewファイルで使いたい変数の名前','$テーブル情報を代入した変数')
  }

クエリビルダでデータベースを操作

データベースの操作にはEloquentの他にクエリビルダという方法が存在し
クエリビルダもSQL文を使わずにデータベースを操作する仕組みですが
Eloquentと異なる点として、

モデルを使わない
Eloquentよりもコードの記述量が多くなる
一部の操作にはSQL言語の構文を使用します

通常はEloquentを利用しますが
SQL文でのデータベースの操作に慣れた方は
クエリビルダの方が使いやすいと好みが分かれます

クエリビルダにはDBファザードの「宣言」が必要

ファサードとはLaravelなどのフレームワークに備え付された機能を
簡単に呼び出して利用できる仕組み(ライブラリ)です。

例としてファザードとは窓口に例えられます。

銀行の窓口でお金を下ろしたい場合、「お金を下ろしたい」と窓口に伝えます。
(Laravelではつまり「データベースを操作したい」と伝えます)

さらに窓口の担当者へ「口座」「金額」などを伝えることで、指定した金額を
渡してくれます。
(Laravelではつまり「テーブル名」「カラム名」を伝えることで必要なレコードを
渡してくれます)

LaravelのフレームワークではDBファザードを利用するためにはコードを記述するファイルごとに
「ここでDBファザードを使います!」という意味の「宣言」してからでなくては使えません。

クエリビルダでDBを操作するためにはDBの窓口を用意する必要があり
その役割がLaravelでは宣言ということにあります。

宣言する方法は操作するphpファイルの初めに次の一文をそのまま記述します

use Illuminate\Support\Facades\DB;

似たような操作方法ではHTMLで言うheadタグに入力する情報のように
DBファザードをuse宣言しなければ利用できません

Eloquentでも操作を実行する上でいくつかのファザードが必要ですが
Eloquentに必要な操作の宣言は
ターミナルコマンドプロンプトでモデルを生成した場合に
デフォルトで記述されているので安心です。

モデルを生成時にデフォルトで用意されているファザードには
HTMLのフォームから送られた情報を利用するための
Requestファザードや自分自身(モデル)を呼び出すためのファザードもあります

use Illuminate\Http\Request;
use App\Companies;
use App\Products;
use Illuminate\Support\Facades\DB;

クエリビルダの記述方法

// テーブルの全要素を取得したいとき。
$products = DB::table('products')->get();

$変数名 = DB::table('テーブル名')->get();

Laravelの機能(ファザード)を呼び出す方法

クエリビルダ方式でDBファザードを使うには以下の書き方をします

ファザード名::メソッド名A(引数)->メソッド名B


$products = DB::table('products')->get();

 DBファザードを呼出しテーブル操作メソッドを実行
    'productsテーブル'の情報を全てゲットする
    取得した情報は'$products'という変数に代入する

モデルからテーブル情報を参照する方法

例えばモデルから関連するテーブルのレコードを取得する際も同じような記述方法になります

 $companies = Companies::query();
 companiesテーブルから全てのレコードを取得する操作を実行
    取得した情報は'$products'という変数に代入する

テーブル情報を代入した変数よりレコードを参照する方法

変数からレコードを操作する場合はモデルやファザードの際とは異なり
矢印を使います。

where句は次のように矢印で連結させて使うことができ、
情報を取得するには最後に->get();で締めます。

 $companies->where('id', '>=',$upper);
 任意の変数からに代入されているレコードから特定の条件でデータを取得
    
    取得した情報は'$products'という変数に代入する

    

    where句で条件を複数設定したい場合
    $companies = Companies::where('this', '=', 1)
    ->where('同じテーブルのカラム名', '=', 1)
    ->where('同じテーブルのカラム名', '<=', 1)
    ->where('同じテーブルのカラム名', '>=', 1)
    ->get();

    

キーワード検索

キーワードを検索するためにはまず
HTMLのフォームに検索フォームを用意する必要があります。
inputタグformタグから送られたデータをコントローラーで受け取り
データベースから該当するレコードのみを取得します。

スクリーンショット 2022-11-28 10.01.22.png

index.blade.php

<!--参考例--> 


<!-- 検索機能ここから -->
<div>
  <form action="{{ route('crud.index') }}" method="GET">

  @csrf

    <input type="text" name="keyword">
    <input type="submit" value="検索">
  </form>
</div>


<!-- 新規作成ボタン -->
<button style="margin-top:50px; margin-bottom:20px;" class="btn btn-primary" type=button onclick="location.href='/create'">新規作成</button>

<!--テーブル-->

      <div class="table-responsive">
          <table class="table" style="width: 1000px; max-width: 0 auto;">
                <tr class="table-info">
                <th scope="col" >id</th>
                  <th scope="col" >商品画像</th>
                  <th scope="col" >商品名</th>
                  <th scope="col" >価格</th>
                  <th scope="col" >在庫数</th>
                  <th scope="col" >メーカー名</th>
                  <th scope="col" >詳細表示</th>
                  <th scope="col" >削除</th>
                </tr>
                
                <!--レコードの繰り返し処理--> 

                @foreach($posts as $companie)
                <tr>
                  <td>{{$companie->id}}</td>
                  <td><img style="width:80px;" src="{{asset($companie->products->img_path)}}" ></td>
                  <td>{{$companie->products->product_name}}</td>
                  <td>{{$companie->products->price}}</td>
                  <td>{{$companie->products->stock}}</td>
                  <td>{{$companie->company_name}}</td>
                  <td><a href="/show/{{$companie->id}}"><button type="button" class="btn btn-success">詳細</button></a></td>

                  <td>


                  <form  class="id">

                         <input data-user_id="{{$companie->id}}" type="submit" class="btn btn-danger btn-dell" value="削除">

                  </form>


                  </td>

                </tr>
                @endforeach
            </table>
       </div>

    {{ $posts->links() }}

    </div>
web.php
Route::get('/', 'HomeController@index')->name('crud.index'); /* 一覧表示 */

HomeContloller.php
    public function index(Request $request)
    {

         /* テーブルから全てのレコードを取得する */
           $companies = Companies::query();


        /* キーワードから検索処理 */
        $keyword = $request->input('keyword');
        if(!empty($keyword)) {//$keyword が空ではない場合、検索処理を実行します
            $companies->where('company_name', 'LIKE', "%{$keyword}%")
            ->orwhereHas('products', function ($query) use ($keyword) {
                $query->where('product_name', 'LIKE', "%{$keyword}%");
            })->get();

        }

        /* ページネーション */
        $posts = $companies->paginate(5);

        return view('crud.index', ['posts' => $posts]);

    }

操作対象のレコードを絞り込む条件を指定するwhere句

where句の引数に条件式を指定することで合致するレコードを取得することができます。

SQL文でも使われる操作方法ですがEloquentやクエリビルダでも利用できます。

WHERE句で部分一致検索をする方法

$対象の変数->where('対象のカラム名', 'like', "%{$キーワードを代入した変数}%")->get();

LIKE句はSQLで曖昧検索をする際に利用するクエリです。
対象のカラムに対して、部分一致する文字列検索をかけることができます

対象の変数($keyword)を%で囲むことで前後一致で取得することができます
%何でも良いので空文字列を含む任意の長さの文字列が一致することを指します

第3引数は""ダブルクォーテーションで囲まなければ変数の中身が展開されません
第3引数を''シングルクォーテーションで囲むと
$keyworeをそのまま文字で検索してしまうため注意が必要です。

WHERE句で複数のカラムから検索する

$対象の変数->where('対象のカラム名', 'like', "%{$キーワードを代入した変数}%")
         ->orwhereHas('対象のモデル名', function ($query) use ($キーワードを代入した変数){
  $query->where('対象のカラム名', 'LIKE', "%{$キーワードを代入した変数}%");
       })->get();

// $queryは任意の名前であり、公式サイトで$queryと紹介されているため利用しています
// $qなどに省略しても機能します

"or"はまたはという意味合いになるため、orwhereHasは別のキーワードも
フィルターにかけるメソッドになります。

イメージとしてデータベースの全レコードが代入された$対象の変数から
whereメソッドorwhereHasメソッドでキーワードに該当するレコードのみを選択し
最終的にgetメソッドでレコードを取得し上書きします。

キーワード処理のまとめ

1. 検索キーワードの送信

ブラウザにはinputタグで作成した入力枠を用意することで
検索ボタンを押したタイミングでinputタグへ入力されたキーワードが
コントローラーのindexアクションに送信される仕組みを作ります

注意点として、Laravelでデータを送信する場合、トークンを利用しなければ
Laravelに標準で備わっているセキュリティで送信が阻害され、
データがコントローラーまで届きません。

よって、formタグの中には必ず@csrfを入力しトークンを同時に送信します

詳しくはcsrfを検索してください。

index.blade.php
<form action="{{ route('crud.index') }}" method="GET">

  @csrf

    <input type="text" name="keyword">
    <input type="submit" value="検索">
  </form>

2. コントローラー側で受け取ったキーワードを基準に判別する

Laravelではinputから送られた値はRequestに代入されているため
まずは変数として利用するためには任意の変数へ代入する必要があります。
一般的にはコードを判別しやすくするため、$requestに代入し利用します。

代入する方法は=(イコール)などで代入するわけではなく次のように
関数名の引数内に並べて記述することで任意の変数へ代入されます

public function index(Request $request)
HomeController.php
    public function index(Request $request)
    {// Request を $requestに代入する

         /* テーブルから全てのレコードを取得する */
         $companies = Companies::query();
        
        
         /* キーワードから検索処理 */
         // 任意の変数に受け取った送信された情報を代入します
         // htmlのinputタグにはname属性に対して'keyword'と設定されているため
         // $keywordへ$requestの中から、nameが'keyword'のinputを代入します
         
            $keyword = $request->input('keyword');
         if(!empty($keyword)) { //もしも、$keywordの中身が空ではない場合に検索処理実行
             $companies->where('company_name', 'LIKE', "%{$keyword}%")
             ->orwhereHas('products', function ($query) use ($keyword) {
                 $query->where('product_name', 'LIKE', "%{$keyword}%");
             })->get();
        
         }

        /* ページネーション */
        // レコードが例えば100件あった場合、一気に表示するとスクロールが面倒なので
        // 分割して表示することをページネーションと呼びます
    
        // 以下は5件ずつ表示する設定を組んでいます
        // $postsは任意の変数名ですが、利用する場合はhtml側と同じ変数名にする必要があります。
        $posts = $companies->paginate(5);

        // index.braid.phpを開き直し、view(HTML)で利用する変数postsに対して
        // コントローラーで作成した$postsを渡します
        return view('crud.index', ['posts' => $posts]);

    }

上限値と下限値を設定しリレーション先を検索する

ここでのリレーションとは関連するテーブルという意味合いです。

データベースのテーブルが複数存在する場合、例えば

Aテーブル

ID 名前 学年 クラス
1 石川 太郎 5 1
2 田中 一郎 5 1

Bテーブル

ID A_ID 身長 体重
1 1 135 45
2 2 140 50

名前,住所,生年月日が記述されたAテーブルに対し
身長,体重が記述されたBテーブルがあった場合、
それぞれのテーブルはIDで紐付けされ、関連するテーブルとして利用されることになります。

Aテーブルを取得する際にBテーブルも追従して取得できるようにすることを
リレーションと呼びます。

関連するデータを取得したい場合は予めリレーション設定による
関連付けを行いデータを取得できるようにします

課題として例えば、身長が140以上のレコードのみを取得したい場合
Bテーブルからwhere句で該当するレコードを取得すれば良いように思えますが
Aテーブルとセットで取得したい場合はリレーションを活用します

リレーションの設定(テーブル側)

各々のテーブルの関連付けには主キー外部キーが必要です
主キーとはそれぞれのレコードを判別するための他と重複しない値、つまりIDのことです

外部キーとはテーブルの関連付けに利用されるBテーブル側のカラムのことで
ここではA_IDという列が外部キーに当たります。
意味としては単純で、AテーブルのIDなのでA_IDになります。

Laravelではリレーション元のテーブル名とIDを_で接続したカラム名が
外部キーとして判別されるため、特に不都合がなければA_IDのように
カラム名を作成し外部キーを作成します。

リレーションの設定(モデル側)

リレーションを設定するにはモデル内に対して、
アクション、つまりリレーションをするという処理を
記述しておく必要があります


class Companies extends Model
{
    Public function products()
  {
    //  Appフォルダに格納されているProductsモデルのデータをリレーションする
    return $this->hasOne('App\Products');
  }

リレーション設定の種類

今回の目的上、従属するテーブルから取得したいレコードは1つだけなので
メインとなるテーブルのモデルに対してhasOneメソッドで設定しましたが
次のようにリレーション設定には種類があります。

  • hasOne(1対1)
    主テーブルのあるレコードに対して、従テーブルの1つのレコードが紐付けられるときに用いられます。
  • hasMany(1対多)
    主テーブルのあるレコードに対して、従テーブルの複数のレコードが紐付けるときに用いられます。
  • belongsTo
    従テーブルの複数レコードに対して、主テーブルの1つのレコードが紐付けるときに用いられます。

HTML側のフォーム

キーワード検索時の応用で送信先を指定し、フォームに入力された情報を
コントローラへ渡します

この際もLaravelのセキュリティで送信エラーが発生しないように
@csrfをフォーム内部に入力することでトークンを送信し
エラーを回避するよう努めます

見本ではレコードを表示する繰り返し処理の後(@foreach)に対して
ページネーション(分割して表示)のコードが記述されています。

後述してありますがコントローラー側でページネーションのコードが記述されている場合
HTML側でもページネーションを出力するコードが記述されていなければエラーとなります

{{ $posts->links() }}
index.braid.php
<div>
  <form action="{{ route('crud.index') }}" method="GET">

  @csrf
  <ul style="list-style:none;">
    <li>検索条件を入力(一部空欄でも検索可能)</li>
    <li><input placeholder="キーワードを入力" type="text" name="keyword"></li>
    <li><input placeholder="上限値を入力" type="text" name="upper"></li>
    <li><input placeholder="下限値を入力" type="text" name="lower"></li>
    <li><input type="submit" value="検索"></li>
  </ul>


  </form>
</div>

~~~

@foreach($posts as $companie)
                <tr>
                  <td>{{$companie->id}}</td>
                  <td><img style="width:80px;" src="{{asset($companie->products->img_path)}}" ></td>
                  <td>{{$companie->products->product_name}}</td>
                  <td>{{$companie->products->price}}</td>
                  <td>{{$companie->products->stock}}</td>
                  <td>{{$companie->company_name}}</td>
                  <td><a href="/show/{{$companie->id}}"><button type="button" class="btn btn-success">詳細</button></a></td>

                ~~~~~~~~~~~~~~~

                </tr>
                @endforeach
            </table>
       </div>

    {{ $posts->links() }}

    </div>


コントローラー側のアクション

イメージとして、まずは全てのレコードを任意の変数に代入し
レコードを代入した任意の変数からwhere句で少しずつ
条件を絞っていく流れになります。

以下の例では、キーワード,最大値,最小値の順で処理を実行していますが
各々の処理の最後のgetメソッドで絞り込みされたレコードが蓄積されるため
段々と目的のレコードを絞り込むことができます

where句の内部には変数を用意しておりますが、
変数の値が空だった場合は、エラーが発生します

情報はinputタグに入力された値が送信されるため
inputの値が一部でも空欄だった場合エラーになるため
今回はIF文で各々の値が空だった場合は検索をスキップするように
コードを記述しています

if(!empty($keyword)) は$keywordに値がある場合にのみ実行する
ことを指します


public function index(Request $request)
    {

 /* テーブルから全てのレコードを取得する */
    $companies = Companies::query();

    $keyword = $request->input('keyword'); //キーワード
    $upper = $request->input('upper'); //最大値
    $lower = $request->input('lower'); //最小値

    /* キーワードから検索処理 */

    if(!empty($keyword)) {

      $companies->where('company_name', 'LIKE', "%{$keyword}%")
      ->orwhereHas('products', function ($query) use ($keyword) {
          $query->where('product_name', 'LIKE', "%{$keyword}%");
      })->get();
      }

    /* 最大値から検索処理 */
        if(!empty($upper)) {
    
          $companies->whereHas('products', function ($q)use($upper) {
            $q->where('id', '>=',$upper);
              })->get();
    
            }
    /* 最小値から検索処理 */
        if(!empty($lower)) {

          $companies->whereHas('products', function ($q)use($lower) {
            $q->where('id', '<=',$lower);
              })->get();

        }

     /* ページネーション */
    // 5レコードずつ表示する
    $posts = $companies->paginate(5);
        

       return view('crud.index', ['posts' => $posts]);
    // ページを開き直すとともに、$postsの情報をHTMLへ送る
    
}

リレーション先の別のモデルから条件を設定して検索したい場合のメソッド

別のモデルからレコードを検索するにはwhereHasメソッドを使います


$companies = Companies::query();
companiesテーブルの全てのレコードを代入


$任意の変数名->whereHas('リレーション先のモデル名', function ($任意の変数名A)use($whereHas内に渡したい変数名B) {
            $任意の変数名A->where('id', '<=',$whereHas内に渡したい変数名B);
              })->get();

Companiesテーブルにリレーション先のモデルから"変数B"を検索し検索したレコードを取得し上書きする


$companies->whereHas('products', function ($q)use($lower) {
$q->where('id', '<=',$lower);
  })->get();
Companiesテーブルにリレーション先のproductsテーブルから"$lower"を検索し検索したレコードを取得し上書きする

whereHasに変数を渡す

whereHasに変数を渡したい場合、
$qと一緒の引数に変数を記述しても内部に変数を渡せずエラーが発生します
use( 変数 )を記述することで、変数を扱うことができます。

21
23
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
21
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?