Laravelで超簡単に検索機能を作成するPackageを作成した

この記事は[Laravel #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/laravel-2)の2日目になります。

## 検索機能？

Webアプリを作成していると、よく検索機能作成しますよね？
↓こんな検索画面です。
![2019-12-01_21h39_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49429/8a8337ce-f36a-6204-089b-efb60778f734.png)

このくらいシンプルであれば、`ifやEloquentを使って条件を追加する`のも悪くないですが、検索項目が多くなってきたり、複雑になってくると、途端に可読性が悪くなったり、めんどくさくなったりすると思います。

その為、Laravelの検索機能の作り方や、検索に特化したPackageなどを、ググったりしたのですが、簡単に検索を実装できるものがなかったので作ってみました。

## 作ったPackage

https://github.com/fusic/Seaaaaarch/


### :sparkles:作るときに気にしたポイント

- 簡単に検索機能を実装できること(検索実装の為に処理をなるべく書きたくない)
- どんな状況にも柔軟に検索できること
- Laravelっぽいこと

### :sparkles:Seaaaaarchで出来るようになること

- Searchableクラスを作成することで、検索条件を書く場所を別クラスに分けれる
- 検索条件にcallbackを受け付けることで、柔軟に検索できる
- 簡単な検索は設定を書くだけで検索を実装できる
- Filterクラスを作成することで、検索条件を共通化できる

## 基本的な使い方

### インストール

```php
composer require fusic/Seaaaaarch
```

### bladeの準備

index.blade.php等に検索用の画面を作成します。
ここは普通のbladeになります。
※名前と住所で検索することを想定

```php:index.blade.php
<form action="{{ route('users.search') }}" method="post">
    @csrf
    <div class='row'>
        <div class='col-md-6'>
            <div class='form-group'>
                <input name="name" />
            </div>
        </div>
        <div class='col-md-6'>
            <div class='form-group'>
                <input name="address" />
            </div>
        </div>
    </div>
    <div style="text-align: center;">
        <button class='btn-default'>検索</button>
    </div>
</form>
```

## Searchableの作成

Seaaaaarchをインストールすると、`Searchable`が作成できるようになります。
**Searchableは検索の実態を記載するクラスになります。**

```php
php artisan make:searchable UsersSearch
```

シンプルな検索であれば、下記のような`設定だけで検索を実装`することが可能になります。

```php:UsersSearch.php
<?php
namespace App\Search;

use Search\Searchable;

class UserSearch extends Searchable
{
    public function __construct()
    {
        $this->params = [
            // ここに検索条件を記載します
            // 名前 と 住所を検索できるようにします
            'name' => [
                // typeにvalueを設定すると、入力値を完全一致で検索します。
                'type' => 'value'
            ],
            'address' => [
                // typeにvalueを設定すると、入力値を部分一致で検索します。
                'type' => 'like'
            ],
        ];
    }
}
```
※検索用にvalue、like、in、callback等準備しています。
※Searchableの詳細な設定項目については[Searchableの設定](https://github.com/fusic/Seaaaaarch/blob/master/docs/ja/settings.md)を参照してください。

### コントローラーに検索用アクションを作成

検索用に、上記で作成したformからPOSTを受け取るアクションを作成します。
QueryParser::parse に Searchableクラス のインスタンスを渡すことで、POST値から検索に必要な値を取り出します。
※routesの設定してください。

```php:UsersController.php
public function search() {
    $query = QueryParser::parse(new UsersSearch());
    return redirect()->route('users.index', $query);
}
```

### 検索の実行

SeaaaaachをインストールするとEloquentに`searchメソッド`を追加します。
`search`に対してSearchableインスタンスを渡すことで、 Searchableに設定された検索条件で検索を行います。
`search`メソッドの前後にEloquentの機能で条件やソートなどを追加することも可能です。

```php:UsersController.php
public function index() {
    UserModel::search(new UserSearch())->where('example', 'test');
    // or
    UserModel::where('example', 'test')->search(new UserSearch())
}
```

## 柔軟な検索

Searchableに検索条件を書くことになりますが、完全一致や部分一致ではなく、`JoinやPHPの処理を挟む`等複雑な処理をすることもあると思います。
そういう状況では、`Searchableのcallback`を利用します。

**callbackの引数に渡ってくる`$builder`でEloquentの機能を利用することが出来ます。**

```php:UsersSearch.php
<?php
namespace App\Search;

use Search\Searchable;

class UserSearch extends Searchable
{
    public function __construct()
    {
        $this->params = [
            'name' => [
                // typeにcallbackを指定すると、methodに検索処理を書くことが出来ます。
                'type' => 'callback',
                'method' => function (Builder $builder, $key, $value) {
                    // ここで条件を組み立てます
                    $name = mb_convert_kana($value, 'Hc');
                    $builder->where('name', $name);
                }
            ]
        ];
    }
}
```

## 処理の共通化

プロジェクト全体等、共通的に利用したい検索条件を作成する機能も`Seaaaaarch`は準備しています。
Filterクラスを作成することで共通化することが可能です。

#### Filterの作成

Filterを作成することで、検索条件を作成することが可能です。

```php
php artisan make:filter ExampleFilter
```

```php:App\Search\Filter\ExampleFilter.php
<?php
namespace App\Search\Filter;

use Illuminate\Database\Eloquent\Builder;
use Search\Filter\Filter;
use Search\Filter\FilterInterface;

class ExampleFilter extends Filter implements FilterInterface {
    protected $defaultOptions = [];

    public function process(Builder $builder, $field, $value)
    {
        // ここに検索条件の処理を記載します
        // 例：20歳以上を検索条件にする
        $builder->where('age', '>=', 20);
    }
}
```

#### Filterの使い方

```php:UsersSearch.php
<?php
namespace App\Search;

use Search\Searchable;

class UserSearch extends Searchable
{
    public function __construct()
    {
        $this->params = [
            'age' => [
                // この指定で、20歳以上の条件がかかるので検索条件の共通化をすることが可能です。
                'type' => ExampleFilter::class
            ]
        ];
    }
}
```

## その他細かい利用方法

GitHubに記載してありますので確認してください。
https://github.com/fusic/Seaaaaarch/tree/master/docs/ja

気に入ったらPull requestやstarよろしくお願いします :bow_tone1: