【Laravel】検索およびフィルタリングの作り方

# はじめに
この記事はLaravelの学習過程の備忘録になります

修正点および加筆が必要な場合は随時更新します

## リクエストの種類（GET/POST)

検索やフィルタリングに関するリクエストは、一般的にGETリクエストが使用されます。GETはデータの取得を目的としており、検索条件をクエリパラメータとしてURLに含めることができるためです。

しかし、検索条件が複雑であったり、大量のデータを送信する必要がある場合、あるいは機密情報を含む場合は、POSTリクエストが使われることもあります。この場合、検索条件はリクエストボディに含めて送信されます。

## メソッドの解説(GETの場合)

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>リクエストの検索フォーム</title>
</head>
<body>
    <h1>顧客検索フォーム（GETリクエスト）</h1>
    <form action="http://localhost:8000/api/customers" method="GET">
        <label for="name">顧客名:</label>
        <input type="text" id="name" name="name" placeholder="名前を入力してください"><br><br>

        <label for="email">メールアドレス:</label>
        <input type="email" id="email" name="email" placeholder="メールアドレスを入力してください"><br><br>

        <button type="submit">検索</button>
    </form>
</body>
</html>

<h1>顧客検索フォーム（POSTリクエスト）</h1>
    <form action="http://localhost:8000/api/customers" method="POST">
        <label for="name">顧客名:</label>
        <input type="text" id="name" name="name" placeholder="名前を入力してください"><br><br>

        <label for="email">メールアドレス:</label>
        <input type="email" id="email" name="email" placeholder="メールアドレスを入力してください"><br><br>

        <!-- CSRFトークンを追加するための隠しフィールド -->
        <input type="hidden" name="_token" value="{{ csrf_token() }}">

        <button type="submit">検索</button>
    </form>
```
```api.php
use App\Http\Controllers\YourController;

Route::get('/customers', [YourController::class, 'search']);
Route::post('/customers', [YourController::class, 'search']);
```

```検索するメソッドの場合.php
public function search(Request $request)
{
    $query = $request->input('query'); // ユーザーからの検索キーワードを取得

    // 顧客名またはメールアドレスがキーワードに一致するレコードを取得
    $customers = Customer::where('name', 'LIKE', "%$query%")
                        ->orWhere('email', 'LIKE', "%$query%")
                        ->get();

    return response()->json($customers); // 結果をJSON形式で返す
}

```
```フィルタリングの場合.php
public function search(Request $request)
{
    // リクエストから'name'と'email'のパラメータを取得
    $name = $request->input('name');
    $email = $request->input('email');

    // Customerモデルからクエリビルダのインスタンスを作成
    $query = Customer::query();

    // 名前フィルタがあれば、クエリに追加
    if ($name) {
        $query->where('name', 'LIKE', "%$name%");
    }

    // メールフィルタがあれば、クエリに追加
    if ($email) {
        $query->where('email', 'LIKE', "%$email%");
    }

    // クエリを実行して結果を取得
    $customers = $query->get();

    // 結果をJSON形式で返す
    return response()->json($customers);
}
```

## コントローラーで使用できるワイルドカード
```sql
-- 'A'で始まる顧客名を検索
SELECT * FROM customers WHERE name LIKE 'A%';

-- 'A'で終わるメールアドレスを検索
SELECT * FROM customers WHERE email LIKE '%A';

-- 'A'を含む顧客名を検索
SELECT * FROM customers WHERE name LIKE '%A%';

-- 1文字だけの顧客名を検索
SELECT * FROM customers WHERE name LIKE 'A_';

-- 2文字の顧客名を検索
SELECT * FROM customers WHERE name LIKE '__C';
```
## 例外処理について
Laravelでは`try-catchブロック`を使用して例外処理を行います。tryブロック内にエラーが発生する可能性のあるコードを配置し、catchブロックでそのエラーをキャッチして処理します。

```php
public function search(Request $request)
{
    // 入力からの顧客名とメールアドレスを取得
    $name = $request->input('name');
    $email = $request->input('email');

    // クエリビルダのインスタンスを作成
    $query = Customer::query();

    try {
        // 名前フィルタがあれば、クエリに追加
        if ($name) {
            $query->where('name', 'LIKE', "%$name%");
        }

        // メールフィルタがあれば、クエリに追加
        if ($email) {
            $query->where('email', 'LIKE', "%$email%");
        }

        // クエリを実行して結果を取得
        $customers = $query->get();

        // 結果が存在しない場合
        if ($customers->isEmpty()) {
            return response()->json(['message' => '該当する顧客が見つかりませんでした。'], 404);
        }

        // 結果をJSON形式で返す
        return response()->json($customers);

    } catch (\Exception $e) {
        // 例外が発生した場合の処理
        return response()->json(['error' => 'エラーが発生しました: ' . $e->getMessage()], 500);
    }
}
```

### コードの補足(例外）

#### \Exception
PHPに最初から組み込まれている例外クラスでエラー処理に関する機能を提供します。$eという変数を用意することでクラスをインスタンス化しています。
- **getMessage()**
例外が発生した際のエラーメッセージを取得します

- **getCode()**
例外のエラーコードを取得します

- **getFile()**
例外が発生したファイル名を取得します

- **getLine()**
例外が発生した行番号を取得します

- **getTrace()**
例外発生時の実行履歴を配列として取得します

- **getTraceAsString()**
実行履歴を文字列として取得します

#### ['error' => 'エラーが発生しました: ' . $e->getMessage()],500

配列のキー（error)に対して以下の値を代入しています
`エラーが発生しました: [具体的なエラーメッセージ]`
「.（ドット）」は文字列の連結のために使われています

500はステータスコードを指定しており、「内部サーバーエラー」を示しています

## バリデーションについて

```php
namespace App\Http\Controllers;

use App\Models\Customer; // Customerモデルをインポート
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class CustomerController extends Controller
{
    public function search(Request $request)
    {
        // バリデーションルールの定義
        $validator = Validator::make($request->all(), [
            'name' => 'nullable|string|max:255',  // 名前は文字列、255文字以内、nullも許可
            'email' => 'nullable|email|max:255',  // メールアドレスは有効なメール形式、255文字以内
        ]);

        // バリデーションのチェック
        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        // バリデーションが成功した場合、検索処理を実行
        $name = $request->input('name');
        $email = $request->input('email');

        // クエリビルダのインスタンスを作成
        $query = Customer::query();

        // 名前フィルタがあれば、クエリに追加
        if ($name) {
            $query->where('name', 'LIKE', "%$name%");
        }

        // メールフィルタがあれば、クエリに追加
        if ($email) {
            $query->where('email', 'LIKE', "%$email%");
        }

        // クエリを実行して結果を取得
        $customers = $query->get();

        // 結果が存在しない場合
        if ($customers->isEmpty()) {
            return response()->json(['message' => '該当する顧客が見つかりませんでした。'], 404);
        }

        // 結果をJSON形式で返す
        return response()->json($customers);
    }
}

```
### コードの補足(バリデーション）

#### Validator::make()
Laravelにおけるバリデーションを実施するためのメソッドです
第一引数: バリデーションのデータ（リクエストから取得した入力データ）を指定します
第二引数: バリデーションルールを配列で指定します。
#### $request->all()
リクエストに含まれるすべての入力データを取得するメソッドです
特定のフィールドだけをバリデーションしたい場合は、
`$request->only(['name', 'email'])`
などを使用して、必要なフィールドだけを取得できます

#### fails()
バリデーションが失敗した場合、trueを返します。失敗した場合には、422ステータスコード（「バリデーションエラー」）とともにエラーメッセージを返します。