Laravel Eloquent Collection で検索機能実装

## Eloquent Collection で絞り込み
Laravel で検索機能を実装すると、Eloquent Builder の where句 で絞り込むことが多いかと思います。
（下記のイメージ）

```php5:EloquentBuilderのwhere句絞り込み

$productQuery = Product::query();
if ($request->input('price')) {
    $productQuery->where('price', '=' ,$request->input('price'));
}

if ($request->input('name')) {
     $productQuery->whereHas('user.userDetail', function (Product $productQuery) use ($request) {
         $productQuery->where('name', '=', $request->input('name'));
     });
 }

$products = $productQuery->get();
dd($products->get());
```

**今回は、Eloquent Collection の filterメソッド を使って絞り込んでみようと思います。**
filterメソッドはtrueまたはfalseを返します。

```php5:EloquentCollectionの絞り込み

$productQuery = Product::cursor();

if ($request->input('price')) {
     $priceCursor = $productQuery->filter(function(Product $productQuery) use ($request) {
        return $productQuery->price === $request->input('price');
     });
    $products = $priceCursor->all();
}else{
    $products = $productQuery->all();
}

dd($products);
```

**※間違えているところがございましたらご指摘いただけると幸いです。**

## Eloquent Builder と Eloquent Collection の併用

あんまりないパターンだとは思いますが、検索項目の一部のみ Collection で絞り込む場合。
基本は Eloquent Builder で統一させるはずなので、なにかしらの諸事情によりやらなければならなくなったとき用に備忘録として残します。

## 流れ

1. Eloquent Builder で SQL に where 付与
2. SQL 実行して結果を LazyCollection (cursor) で取得
3. LazyCollection::filter() で結果をフィルタリング



```php5:１．EloquentBuilderでSQLにwhere付与

$productQuery = Product::with('user.userDetail');
$productQuery->where('colors', $request->input('colors'));

```

```php5:２.　SQL実行して結果をLazyCollectionのcursorメソッドで取得

// cursor()した時点でLaravel6.x であれば LazyCollectionになる
$productCursor = $productQuery->cursor();

```

```php5:３．LazyCollectionのfilter()で結果をフィルタリング
if ($request->input('name')) {
    $filtered = $productCursor->filter(function (Product $app) use ($request) {
        // filter()のコールバック関数の引数はBuilderではなくModelになりRelationshipsが使える
        return $app->user->userDetail->name === $request->input('name');
    });
$products = $filtered->all();

```

Eloquent Builder は基本、操作がミュータブルなので $productQuery の状態を変化（Builder自身を変化）しますが、
Eloquent Collection の filerメソッドは操作がイミュータブルな（自身の状態を変えずに新しいCollectionを作る）ので、変数をあてる必要があります。
ミュータブルかイミュータブルかどうかは、\Illuminate\Support\Collection のソースを見ます。
(戻り値が Collection ならだいたいイミュータブルではあるそうです。)

https://github.com/illuminate/support/blob/master/Collection.php

**@ return staticとなってるのがイミュータブル**
**@ return $thisとなてるのはミュータブル**

だそうです。
実際に今回使った filter() を確認してみると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308965/4dbc7c16-212c-dd7d-c8b4-330d9d7ce4f7.png)

***return new static*** とあるのでイミュータブルです。



##流れのソースまとめ

```php5:商品テーブルの料金検索とネスト先の名前検索

$productQuery = Product::with('user.userDetail');
if ($request->input('price')) {
        $productQuery->where('price', $inputs['price']);
    });

$productCursor = $productQuery->cursor();

if ($request->input('name')) {
    $filtered = $productCursor->filter(function (Product $app) use ($request) {
        return $app->user->userDetail->name === $request->input('name');
    });
    $products = $filtered->all();
}else{
    $products = $productCursor->all();
}

```


## 参考文献

この記事は以下の情報を参考にしました。


- @mpywさん
- nさん(@n_1215)