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

# 実装する検索機能一覧

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

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

![スクリーンショット 2022-12-03 21.40.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/e75b1ed6-285f-83fc-830f-1ce4d29ddb3a.png)


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

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

### LaravelでDBを操作するコード


#### Eloquent（エロクエント）
`データベース`とLaravelで作成した`モデル`を対応づける機能です。

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


例として写真のように`productsテーブル`を作成した場合は
Laravelのプロジェクト内には`Products.php`という
`モデル`を作成することになります
![スクリーンショット 2022-12-02 10.45.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/31ea3b31-97a2-9d8a-6ca8-2feb1083a191.png)

```php:products.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

```


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

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

```php
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ファイルの初めに次の一文をそのまま記述します

```php
use Illuminate\Support\Facades\DB;
```

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

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

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

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


#### クエリビルダの記述方法

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

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

```


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

クエリビルダ方式でDBファザードを使うには以下の書き方をします
```php
ファザード名::メソッド名A(引数)->メソッド名B

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

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

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

例えばモデルから関連するテーブルのレコードを取得する際も同じような記述方法になります
```php
 $companies = Companies::query();
訳： companiesテーブルから全てのレコードを取得する操作を実行
    取得した情報は'$products'という変数に代入する

```

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

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

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

```php
 $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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2765288/69c334af-daf7-7655-4749-1df39fc122c8.png)

```php: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>
```

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

```

``` php: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句で部分一致検索をする方法

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

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


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

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

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

```php
$対象の変数->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`を検索してください。

```php: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に代入し利用します。

代入する方法は`＝（イコール）`などで代入するわけではなく次のように
関数名の引数内に並べて記述することで任意の変数へ代入されます
```php3
public function index(Request $request)
```


```php: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`のように
カラム名を作成し外部キーを作成します。



### リレーションの設定（モデル側）

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

```php

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側でもページネーションを出力するコードが記述されていなければエラーとなります
```html
{{ $posts->links() }}
```

```html: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に値がある場合にのみ実行する`
ことを指します



```php

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メソッド`を使います

```php

$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( 変数 )を記述することで、変数を扱うことができます。