laravel8で複合検索

# はじめに

複合検索のメソッドを忘備録としてメモしておきます。

# View
Viewファイルに
・検索フォーム
・ソートリンク付きテーブルヘッダー
・一覧表示テーブル
を用意する。

```blade.php

{!! Form::open(array('url' => '/user/list', 'method' => 'get', 'id'=>'form')) !!}

{{ Form::label('section_id','部署名') }}
{{ Form::select('section_id', config('const.section_name'), old('section_id'), ['class' => 'form-control', 'placeholder' => '選択してください']) }}

{{ Form::label('name','名前') }}
{{ Form::text('name', '', ['class' => 'form-control', 'placeholder' => '氏名']) }}

{{ Form::label('login_id','ログインID') }}
{{ Form::text('login_id', '', ['class' => 'form-control', 'placeholder' => 'ログインID(メールアドレス)']) }}

{{ Form::submit('送信', ['class'=>'btn btn-primary btn-block']) }}

{!! Form::close() !!}

----------------------------------------------------------------------

    <div class="card-body p-0 table-responsive">
        <table id="user-master" class="table table-bordered table-striped">
            <thead>
                <tr>
                    <th class="sorting"><a href="{{ Request::getSortLink('section_id') }}">部署名</a></th>
                    <th class="sorting"><a href="{{ Request::getSortLink('name') }}">氏名</a></th>
                    <th class="sorting"><a href="{{ Request::getSortLink('login_id') }}">ログインID(メールアドレス)</a></th>
                </tr>
            </thead>
            <tbody>
                @foreach($userList as $user)
                    <tr>
                        <td>{{ config('const.section_name')[$user->section_id] }}</td>
                        <td>{{ $user->name }}</td>
                        <td>{{ $user->login_id }}</td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    </div>
    <div class="card-footer clearfix text-right">
        {{ $userList->appends($conditions)->links('vendor.pagination.default') }}
    </div>
```

↑レイアウトは適当です。

# ソートボタンのリンク(URL)生成

まずはソートのリンクを生成する処理を作ります。

```src/app/Http/Request.php
<?php

namespace App\Http;

use Illuminate\Http\Request as BaseRequest;

class Request extends BaseRequest
{

    public function getSortLink($key)
    {
        $orderBy = $this->orderby;
        $sort    = $orderBy == $key && $this->sort == 'desc' ? 'asc' : 'desc';
        $tmp     = $this->fullUrlWithQuery(['orderby' => $key, 'sort' => $sort]);

        return $outputUrl;
    }
}
```

リンクをクリックするとasc⇄descの切り替えをするようにしています。

# Controller

```UserController.php
    /**
     * ユーザー一覧
     * @return type
     */
    public function index(Request $request)
    {
        // ログインユーザーを取得する。
        $userSet = Auth::user();

        $conditions = $request->all(); //ここで検索したGETパラメータを配列で受け取る
        $orderby    = $request->orderby ?: 'section_id'; //ソートするカラムを取得デフォは部署
        $sort       = $request->sort ?: 'asc'; //昇順・降順をセットする
        $users      = User::searchByConditions($conditions);
        $userList   = $this->setOrderBy($users, $orderby, $sort)->paginate(config('const.paginate.user'))->setPath('/user');

        $requestData = compact(
            'userSet',
            'conditions',
            'users',
            'userList'
        );
        return view('user.index', $requestData);
    }

    /**
     * orderByをセット
     * （ソート機能の内部処理をここに実装）
     * @param query $query
     * @param type $orderby
     * @param type $sort
     * @return query
     */
    public static function setOrderBy($query, $orderby, $sort)
    {
        switch ($orderby) {
            default:
                $query = $query->orderBy($orderby, $sort);
                break;
        }
        return $query;
    }
```

この`setOrderBy`ですが、もし、カラム名がinputのname要素と一致しない場合や他テーブルを参照する必要がある場合はcase文をしてやる必要があります。
たとえばこんな感じに。

```.php
        switch ($orderby) {

            //inputのnameは'login_id'だが、DBのカラム名は'mail'の場合
            case 'login_id':
                $query = $query->orderBy('mail', $sort);
                break;

            //articlesテーブルのtitleで検索する場合（Viewは割愛してます）
            case 'title':
                $query = $query->leftJoin('articles', 'users.id', '=', 'articles.user_id')
                    ->orderby('articles.title', $sort);
                break;

            default:
                $query = $query->orderBy($orderby, $sort);
                break;

        }
```

#Model

```User.php

    /**
     * 検索項目定義
     * @var varchar
     */
    public static $searchableFields = [
        'section_id', //部署名
        'name', //氏名
        'login_id', //ログインID
    ];

    /**
     * 検索処理
     * @param array $conditions 検索条件
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public static function searchByConditions($conditions)
    {
        //withにリレーション追記
        $query = self::select('users.*');

        foreach ($conditions as $key => $value) {

            if (!in_array($key, self::$searchableFields) || !isset($value)) {
                continue;
            }
            switch ($key) {
                case 'name':
                case 'login_id':
                    $query = $query->where($key, 'LIKE', '%' . $value . '%');
                    break;
                default:
                    $query = $query->where($key, '=', $value);
                    break;
            }
        }
        return $query;
    }
```

ここで出てくる`searchByConditions($conditions)`でController側の$conditionsに入れたGETパラメータの配列をforeachで回します。
そして、keyであるinputのname要素を基準にusersテーブル内をwhereで検索していき、最終的に`$query`に検索にひっかかったデータ群のみが残るようになります。

ここでもし、inputのnameと実際のカラム名が異なる場合、また、users以外のテーブルから検索する場合の処理を例として記載しておくと、

```User.php

    /**
     * 検索項目定義
     * @var varchar
     */
    public static $searchableFields = [
        'section_id', //部署名
        'name', //氏名
        'login_id', //ログインID
        'title' //投稿記事タイトル（追加）
    ];

    /**
     * 検索処理
     * @param array $conditions 検索条件
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public static function searchByConditions($conditions)
    {
        $query = self::select('users.*')->with('articles');

        foreach ($conditions as $key => $value) {

            if (!in_array($key, self::$searchableFields) || !isset($value)) {
                continue;
            }
            switch ($key) {
                case 'name':
                   $query = $query->where($key, 'LIKE', '%' . $value . '%');
                    break;

                //inputのnameは'login_id'だが、DBのカラム名は'mail'の場合
                case 'login_id':
                    $query = $query->where('mail', 'LIKE', '%' . $value . '%');
                    break;

                //articlesテーブルのtitleで検索する場合（Viewは割愛してます）
                case 'title':
                    $query->whereHas('articles', function ($query) use ($key, $value) {
                        $query->whereIn($key, $value);
                    });
                    break;

                default:
                    $query = $query->where($key, '=', $value);
                    break;
            }
        }
        return $query;
```