LaravelのorWhereメソッド完全ガイド:8つの実践的な使用例と注意点

orWhereメソッドの基礎知識

orWhereメソッドの基本的な構文と動作原理

LaravelのorWhereメソッドは、データベースクエリに「OR」条件を追加するための強力なメソッドです。基本的な構文は以下の通りです:

// 基本的な使用方法
$users = DB::table('users')
            ->where('status', 'active')
            ->orWhere('role', 'admin')
            ->get();

// 生成されるSQL
SELECT * FROM users WHERE status = 'active' OR role = 'admin'

orWhereメソッドは以下の形式で使用できます:

  1. 単純な条件指定
// 単一のカラムと値の比較
->orWhere('column', 'value')

// 演算子を使用した比較
->orWhere('age', '>=', 18)
  1. クロージャを使用した複雑な条件
$users = DB::table('users')
    ->where('status', 'active')
    ->orWhere(function($query) {
        $query->where('role', 'admin')
              ->where('department', 'IT');
    })
    ->get();

// 生成されるSQL
SELECT * FROM users 
WHERE status = 'active' 
OR (role = 'admin' AND department = 'IT')

whereメソッドとの違いと使い分けのポイント

whereメソッドとorWhereメソッドの主な違いは以下の通りです:

  1. 条件の結合方法
  • whereメソッド: AND条件で結合
  • orWhereメソッド: OR条件で結合
// whereメソッドの場合(AND条件)
$users = DB::table('users')
    ->where('status', 'active')
    ->where('age', '>=', 18)
    ->get();
// WHERE status = 'active' AND age >= 18

// orWhereメソッドの場合(OR条件)
$users = DB::table('users')
    ->where('status', 'active')
    ->orWhere('age', '>=', 18)
    ->get();
// WHERE status = 'active' OR age >= 18

使い分けのポイント:

  1. 排他的な条件の場合
// ユーザーが管理者または特別な権限を持っている場合
$users = User::where('is_admin', true)
    ->orWhere('has_special_permissions', true)
    ->get();
  1. 複数の条件をグループ化する場合
// アクティブなユーザーで、管理者または特別な権限を持っている場合
$users = User::where('status', 'active')
    ->where(function($query) {
        $query->where('is_admin', true)
              ->orWhere('has_special_permissions', true);
    })
    ->get();

注意点:

  • orWhereの使用は、クエリのパフォーマンスに影響を与える可能性があります
  • 複雑な条件を組み合わせる場合は、クロージャを使用して適切にグループ化することが重要です
  • インデックスの効果が低下する可能性があるため、必要な場合のみ使用することを推奨します

実装する際の基本原則:

  1. シンプルな条件では直接orWhereを使用
  2. 複雑な条件ではクロージャでグループ化
  3. パフォーマンスを考慮してインデックスを適切に設計
  4. 可読性を保つためにクエリを適切に整形

以上の基礎知識を踏まえた上で、実際の実装例や最適化手法について、続くセクションで詳しく解説していきます。

実践的なorWhereの使用例

基本的な複数条件での検索実装

実務で頻繁に発生する基本的な検索パターンを紹介します。

  1. 複数キーワードでの検索
// ユーザー名またはメールアドレスで検索する例
public function searchUsers($keyword)
{
    return User::where('name', 'LIKE', "%{$keyword}%")
        ->orWhere('email', 'LIKE', "%{$keyword}%")
        ->get();
}

// 検索条件を動的に追加する例
public function advancedSearch($conditions)
{
    $query = User::query();

    if (isset($conditions['keyword'])) {
        $query->where(function($q) use ($conditions) {
            $q->where('name', 'LIKE', "%{$conditions['keyword']}%")
              ->orWhere('email', 'LIKE', "%{$conditions['keyword']}%");
        });
    }

    return $query->get();
}

サブクエリを使用した高度な検索条件の設定

より複雑な検索要件に対応するためのサブクエリの活用方法です。

// 最近の注文がある顧客または優良顧客を検索
$customers = Customer::where(function($query) {
    $query->whereHas('orders', function($q) {
        $q->where('created_at', '>=', now()->subMonths(3));
    });
})->orWhere('customer_rank', 'premium')
  ->get();

// 部署別の売上目標を達成している社員または特別表彰を受けた社員を検索
$employees = Employee::where(function($query) {
    $query->whereHas('sales', function($q) {
        $q->select(DB::raw('SUM(amount)'))
          ->where('sales_date', '>=', now()->startOfYear())
          ->havingRaw('SUM(amount) >= departments.target_amount');
    })->join('departments', 'employees.department_id', '=', 'departments.id');
})->orWhere('has_special_award', true)
  ->get();

関連テーブルを結合した検索条件の実装

複数のテーブルを結合する際の効果的な検索条件の実装方法です。

// 商品カテゴリまたはタグで検索
$products = Product::select('products.*')
    ->leftJoin('product_categories', 'products.category_id', '=', 'product_categories.id')
    ->leftJoin('product_tags', 'products.id', '=', 'product_tags.product_id')
    ->where(function($query) use ($searchTerm) {
        $query->where('product_categories.name', 'LIKE', "%{$searchTerm}%")
              ->orWhere('product_tags.name', 'LIKE', "%{$searchTerm}%");
    })
    ->distinct()
    ->get();

// 注文履歴と顧客ステータスを組み合わせた検索
$orders = Order::select('orders.*')
    ->join('customers', 'orders.customer_id', '=', 'customers.id')
    ->where(function($query) {
        $query->where('orders.total_amount', '>=', 10000)
              ->orWhere('customers.status', 'VIP');
    })
    ->get();

動的な検索条件の組み立て方

ユーザー入力に基づいて動的に検索条件を構築する実装パターンです。

class ProductSearchService
{
    public function search(array $params)
    {
        $query = Product::query();

        // 検索条件を動的に追加
        if (!empty($params)) {
            $query->where(function($q) use ($params) {
                foreach ($params as $field => $value) {
                    switch ($field) {
                        case 'keyword':
                            $q->where('name', 'LIKE', "%{$value}%")
                              ->orWhere('description', 'LIKE', "%{$value}%");
                            break;

                        case 'price_range':
                            $q->orWhereBetween('price', [$value['min'], $value['max']]);
                            break;

                        case 'categories':
                            $q->orWhereIn('category_id', $value);
                            break;
                    }
                }
            });
        }

        return $query;
    }
}

// 使用例
$searchParams = [
    'keyword' => 'デジタル',
    'price_range' => ['min' => 1000, 'max' => 5000],
    'categories' => [1, 2, 3]
];

$products = (new ProductSearchService())->search($searchParams)->get();

実装時の重要なポイント:

  1. クエリの可読性
  • 複雑な条件は適切に関数化する
  • クロージャを使用して条件をグループ化する
  1. パフォーマンスの考慮
  • 必要なカラムのみを選択する
  • JOINの使用は必要最小限に抑える
  • インデックスを効果的に活用する
  1. セキュリティ対策
  • ユーザー入力値は適切にエスケープする
  • SQLインジェクション対策を忘れない
  1. メンテナンス性
  • 検索ロジックは専用のクラスに分離する
  • テストが書きやすい構造にする
  • 条件の追加・変更が容易な設計にする

これらの実装例は、実際の開発現場で遭遇する一般的なケースをカバーしています。次のセクションでは、これらの実装におけるパフォーマンス最適化について詳しく解説していきます。

orWhereメソッドのパフォーマンス最適化

実行されるSQLクエリの解析と改善方法

orWhereを使用したクエリのパフォーマンスを最適化するには、まず実際に実行されるSQLを理解し、その実行計画を分析する必要があります。

クエリの解析方法

  1. クエリログの確認
// クエリログを有効化
DB::enableQueryLog();

// クエリの実行
$users = User::where('status', 'active')
    ->orWhere('role', 'admin')
    ->get();

// 実行されたクエリの確認
dd(DB::getQueryLog());
  1. 実行計画の確認
// 実行計画の取得
$explain = DB::select('EXPLAIN SELECT * FROM users WHERE status = ? OR role = ?', ['active', 'admin']);

// より詳細な実行計画(MySQL 5.7以降)
$explain = DB::select('EXPLAIN FORMAT=JSON SELECT * FROM users WHERE status = ? OR role = ?', ['active', 'admin']);

パフォーマンス改善のポイント

  1. OR条件の最適化
// 改善前
$users = User::where('status', 'active')
    ->orWhere('role', 'admin')
    ->get();

// 改善後(UNIONを使用)
$activeUsers = User::where('status', 'active');
$adminUsers = User::where('role', 'admin');

$users = $activeUsers->union($adminUsers)->get();
  1. サブクエリの最適化
// 改善前
$products = Product::where(function($query) {
    $query->whereHas('orders', function($q) {
        $q->where('created_at', '>=', now()->subDays(30));
    });
})->orWhere('is_featured', true)
  ->get();

// 改善後(サブクエリをJOINに変換)
$products = Product::leftJoin('orders', 'products.id', '=', 'orders.product_id')
    ->where(function($query) {
        $query->where('orders.created_at', '>=', now()->subDays(30))
              ->orWhere('products.is_featured', true);
    })
    ->select('products.*')
    ->distinct()
    ->get();

インデックス設計のベストプラクティス

orWhere使用時の効果的なインデックス設計について解説します。

基本的なインデックス設計

  1. 単一カラムのインデックス
// マイグレーションでのインデックス定義
Schema::table('users', function (Blueprint $table) {
    $table->index('status');
    $table->index('role');
});
  1. 複合インデックスの活用
// 頻繁に組み合わせて使用されるカラムの複合インデックス
Schema::table('products', function (Blueprint $table) {
    $table->index(['category_id', 'status']);
});

高度なインデックス戦略

  1. カバリングインデックス
// 必要なカラムすべてをインデックスに含める
Schema::table('orders', function (Blueprint $table) {
    $table->index(['status', 'created_at', 'total_amount']);
});

// クエリでカバリングインデックスを活用
$orders = Order::select('status', 'created_at', 'total_amount')
    ->where(function($query) {
        $query->where('status', 'pending')
              ->orWhere('created_at', '>=', now()->subDays(7));
    })
    ->get();
  1. 部分インデックス(MySQL 8.0以降)
// 特定の条件に対するインデックス
DB::statement('CREATE INDEX idx_active_users ON users (email) WHERE status = "active"');

パフォーマンス最適化のためのベストプラクティス

  1. クエリの実行計画を定期的に確認
// デバッグ用のクエリログ設定
if (config('app.debug')) {
    DB::listen(function($query) {
        Log::info(
            $query->sql,
            [
                'bindings' => $query->bindings,
                'time' => $query->time
            ]
        );
    });
}
  1. キャッシュの活用
// 検索結果のキャッシュ
$users = Cache::remember('search_results', now()->addMinutes(30), function () use ($searchParams) {
    return User::where(function($query) use ($searchParams) {
        $query->where('status', $searchParams['status'])
              ->orWhere('role', $searchParams['role']);
    })->get();
});
  1. チャンク処理の活用
// 大量のデータを処理する場合
User::where(function($query) {
    $query->where('status', 'active')
          ->orWhere('role', 'admin');
})->chunk(1000, function ($users) {
    foreach ($users as $user) {
        // 処理
    }
});

最適化における重要な考慮点:

  1. インデックスの影響
  • orWhere使用時はインデックスが効きにくい
  • 可能な限りUNIONを使用して最適化
  • 複合インデックスの順序を適切に設計
  1. クエリの実行計画
  • EXPLAINを使用して定期的に確認
  • インデックスの使用状況を監視
  • 実行時間の長いクエリを特定
  1. データ量との関係
  • 大規模データの場合はページネーション必須
  • 適切なチャンクサイズの設定
  • キャッシュ戦略の検討

これらの最適化手法を適切に組み合わせることで、orWhereを使用した複雑な検索条件でも高いパフォーマンスを維持することが可能です。

orWhereメソッドの実装パターン集

ユーザー検索機能での実装例

ユーザー管理システムでよく使用される検索機能の実装パターンを紹介します。

  1. 基本的な検索フォーム実装
class UserSearchService
{
    public function search(array $params)
    {
        $query = User::query();

        if (!empty($params['search_term'])) {
            $query->where(function($q) use ($params) {
                $term = $params['search_term'];
                $q->where('name', 'LIKE', "%{$term}%")
                  ->orWhere('email', 'LIKE', "%{$term}%")
                  ->orWhere('phone', 'LIKE', "%{$term}%");
            });
        }

        // ステータスでの絞り込み
        if (!empty($params['status'])) {
            $query->where('status', $params['status']);
        }

        // 役割での絞り込み
        if (!empty($params['roles'])) {
            $query->where(function($q) use ($params) {
                foreach ($params['roles'] as $role) {
                    $q->orWhere('role', $role);
                }
            });
        }

        return $query->paginate(20);
    }
}

// コントローラでの使用例
class UserController extends Controller
{
    private $userSearchService;

    public function __construct(UserSearchService $userSearchService)
    {
        $this->userSearchService = $userSearchService;
    }

    public function search(Request $request)
    {
        $params = $request->validate([
            'search_term' => 'nullable|string|max:100',
            'status' => 'nullable|in:active,inactive,pending',
            'roles' => 'nullable|array',
            'roles.*' => 'string|in:admin,user,manager'
        ]);

        $users = $this->userSearchService->search($params);

        return view('users.index', compact('users'));
    }
}

商品フィルター機能での活用方法

ECサイトなどで使用される商品検索・フィルター機能の実装パターンです。

class ProductFilter
{
    private $query;

    public function __construct($query)
    {
        $this->query = $query;
    }

    public function apply(array $filters)
    {
        foreach ($filters as $filter => $value) {
            if (method_exists($this, $filter)) {
                $this->$filter($value);
            }
        }

        return $this->query;
    }

    private function keyword($term)
    {
        return $this->query->where(function($q) use ($term) {
            $q->where('name', 'LIKE', "%{$term}%")
              ->orWhere('description', 'LIKE', "%{$term}%")
              ->orWhereHas('categories', function($query) use ($term) {
                  $query->where('name', 'LIKE', "%{$term}%");
              });
        });
    }

    private function price_range($range)
    {
        if (!empty($range['min'])) {
            $this->query->where('price', '>=', $range['min']);
        }
        if (!empty($range['max'])) {
            $this->query->where('price', '<=', $range['max']);
        }
        return $this->query;
    }

    private function categories($ids)
    {
        return $this->query->where(function($q) use ($ids) {
            foreach ($ids as $id) {
                $q->orWhere('category_id', $id);
            }
        });
    }
}

// 使用例
class ProductController extends Controller
{
    public function index(Request $request)
    {
        $query = Product::query();
        $products = (new ProductFilter($query))
            ->apply($request->all())
            ->paginate(24);

        return view('products.index', compact('products'));
    }
}

レポート機能での応用テクニック

管理画面などでよく使用される集計・レポート機能の実装パターンです。

class SalesReportService
{
    public function generateReport(array $params)
    {
        $query = Order::query();

        // 日付範囲での絞り込み
        if (!empty($params['date_range'])) {
            $query->whereBetween('created_at', [
                $params['date_range']['start'],
                $params['date_range']['end']
            ]);
        }

        // 売上高による条件
        if (!empty($params['revenue_condition'])) {
            $query->where(function($q) use ($params) {
                $target = $params['revenue_condition']['target'];
                switch ($params['revenue_condition']['type']) {
                    case 'above_target':
                        $q->where('total_amount', '>', $target)
                          ->orWhereHas('customer', function($sq) {
                              $sq->where('tier', 'premium');
                          });
                        break;
                    case 'below_target':
                        $q->where('total_amount', '<', $target)
                          ->orWhere('status', 'pending');
                        break;
                }
            });
        }

        // グループ化と集計
        $results = $query->select(
            DB::raw('DATE(created_at) as date'),
            DB::raw('SUM(total_amount) as total_revenue'),
            DB::raw('COUNT(*) as order_count')
        )
        ->groupBy(DB::raw('DATE(created_at)'))
        ->orderBy('date')
        ->get();

        return $this->formatReportData($results);
    }

    private function formatReportData($results)
    {
        // レポートデータのフォーマット処理
        return $results->map(function($row) {
            return [
                'date' => $row->date,
                'revenue' => number_format($row->total_revenue, 2),
                'orders' => $row->order_count,
                'average_order_value' => $row->order_count > 0 
                    ? number_format($row->total_revenue / $row->order_count, 2)
                    : 0
            ];
        });
    }
}

実装時の重要なポイント:

  1. コードの構造化
  • 検索ロジックの分離(サービスクラスの活用)
  • フィルター条件の個別メソッド化
  • バリデーションの適切な実装
  1. 保守性の向上
  • 設定値の定数化
  • ドキュメントコメントの追加
  • テストコードの作成
  1. エラーハンドリング
  • バリデーションの実装
  • 例外処理の追加
  • ログ出力の実装

これらの実装パターンは、実際の開発現場での経験に基づいており、様々なプロジェクトで再利用可能な形で設計されています。次のセクションでは、これらの実装における注意点とトラブルシューティングについて解説していきます。

orWhereメソッドの注意点とトラブルシューティング

よくある実装ミスとその対処法

1. 条件のグループ化ミス

// 誤った実装例
$users = User::where('status', 'active')
    ->orWhere('role', 'admin')
    ->where('department', 'sales')
    ->get();

// 期待したSQL:
// WHERE (status = 'active' OR role = 'admin') AND department = 'sales'

// 実際のSQL:
// WHERE status = 'active' OR role = 'admin' AND department = 'sales'

// 正しい実装例
$users = User::where(function($query) {
    $query->where('status', 'active')
          ->orWhere('role', 'admin');
})
->where('department', 'sales')
->get();

2. パラメータのバインディングミス

// 危険な実装(SQLインジェクションの危険性)
$keyword = request('keyword');
$users = User::whereRaw("name LIKE '%$keyword%'")
    ->orWhereRaw("email LIKE '%$keyword%'")
    ->get();

// 安全な実装
$keyword = request('keyword');
$users = User::where(function($query) use ($keyword) {
    $query->where('name', 'LIKE', "%{$keyword}%")
          ->orWhere('email', 'LIKE', "%{$keyword}%");
})->get();

// さらに安全な実装(パラメータのサニタイズ)
$keyword = strip_tags(request('keyword'));
$users = User::where(function($query) use ($keyword) {
    $query->where('name', 'LIKE', '%' . addcslashes($keyword, '%_') . '%')
          ->orWhere('email', 'LIKE', '%' . addcslashes($keyword, '%_') . '%');
})->get();

3. N+1問題の発生

// 問題のある実装
$posts = Post::where('status', 'published')
    ->orWhere('author_id', $currentUserId)
    ->get();

foreach ($posts as $post) {
    echo $post->author->name; // N+1問題が発生
}

// 改善された実装
$posts = Post::with('author')
    ->where('status', 'published')
    ->orWhere('author_id', $currentUserId)
    ->get();

// さらに最適化された実装(必要なカラムのみ取得)
$posts = Post::with(['author:id,name'])
    ->where('status', 'published')
    ->orWhere('author_id', $currentUserId)
    ->get();

デバッグとテストのベストプラクティス

1. クエリログの活用

// デバッグ用のクエリログ設定
class QueryDebugProvider extends ServiceProvider
{
    public function boot()
    {
        if (config('app.debug')) {
            DB::listen(function($query) {
                $sqlWithBindings = vsprintf(str_replace('?', "'%s'", $query->sql), $query->bindings);
                Log::channel('query')->info('SQL: ' . $sqlWithBindings, [
                    'time' => $query->time . 'ms',
                    'connection' => $query->connectionName
                ]);
            });
        }
    }
}

// 使用例
public function searchUsers(Request $request)
{
    try {
        DB::enableQueryLog();

        $users = User::where(function($query) use ($request) {
            $query->where('status', $request->status)
                  ->orWhere('role', $request->role);
        })->get();

        Log::info('Query Log:', DB::getQueryLog());

        return $users;
    } catch (\Exception $e) {
        Log::error('User search failed:', [
            'error' => $e->getMessage(),
            'queries' => DB::getQueryLog()
        ]);
        throw $e;
    }
}

2. ユニットテストの実装

class UserSearchTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_can_search_users_with_or_conditions()
    {
        // テストデータの準備
        $activeUser = User::factory()->create(['status' => 'active']);
        $adminUser = User::factory()->create([
            'status' => 'inactive',
            'role' => 'admin'
        ]);
        $regularUser = User::factory()->create([
            'status' => 'inactive',
            'role' => 'user'
        ]);

        // 検索の実行
        $result = User::where(function($query) {
            $query->where('status', 'active')
                  ->orWhere('role', 'admin');
        })->get();

        // アサーション
        $this->assertCount(2, $result);
        $this->assertTrue($result->contains($activeUser));
        $this->assertTrue($result->contains($adminUser));
        $this->assertFalse($result->contains($regularUser));
    }

    /** @test */
    public function it_properly_handles_empty_search_results()
    {
        // 検索の実行
        $result = User::where(function($query) {
            $query->where('status', 'nonexistent')
                  ->orWhere('role', 'nonexistent');
        })->get();

        // アサーション
        $this->assertCount(0, $result);
        $this->assertTrue($result->isEmpty());
    }
}

デバッグ時のチェックリスト

  1. クエリの確認
  • 生成されるSQLの確認
  • バインディングパラメータの確認
  • 実行計画の確認
  1. パフォーマンスの確認
  • クエリ実行時間の計測
  • メモリ使用量の監視
  • N+1問題の検出
  1. セキュリティの確認
  • SQLインジェクションの可能性
  • パラメータのサニタイズ
  • アクセス制御の確認

トラブルシューティングのベストプラクティス

  1. 段階的なデバッグ
public function debugQuery($params)
{
    // Step 1: 基本クエリの確認
    $query = User::query();
    Log::info('Base Query:', ['sql' => $query->toSql()]);

    // Step 2: 条件追加後の確認
    $query->where(function($q) use ($params) {
        $q->where('status', $params['status'])
          ->orWhere('role', $params['role']);
    });
    Log::info('With Conditions:', ['sql' => $query->toSql()]);

    // Step 3: 実行結果の確認
    $result = $query->get();
    Log::info('Result Count:', ['count' => $result->count()]);

    return $result;
}
  1. エラーハンドリング
public function safeSearch($params)
{
    try {
        return DB::transaction(function() use ($params) {
            $query = User::where(function($q) use ($params) {
                $q->where('status', $params['status'])
                  ->orWhere('role', $params['role']);
            });

            // 結果の検証
            $count = $query->count();
            if ($count > 1000) {
                throw new \RuntimeException('Too many results');
            }

            return $query->get();
        });
    } catch (\Exception $e) {
        Log::error('Search failed', [
            'error' => $e->getMessage(),
            'params' => $params
        ]);
        throw $e;
    }
}

これらの実装例と注意点を意識することで、より安全で保守性の高いorWhereクエリを実装することができます。

orWhereメソッドの応用と代替手段

スコープを使用した検索条件の再利用

複雑な検索条件を再利用可能な形で実装する方法を解説します。

モデルスコープの実装

class Product extends Model
{
    // グローバルスコープ
    protected static function booted()
    {
        static::addGlobalScope('active', function ($query) {
            $query->where('status', 'active');
        });
    }

    // ローカルスコープ
    public function scopeSearchByKeyword($query, $keyword)
    {
        return $query->where(function($q) use ($keyword) {
            $q->where('name', 'LIKE', "%{$keyword}%")
              ->orWhere('description', 'LIKE', "%{$keyword}%");
        });
    }

    public function scopePriceRange($query, $min = null, $max = null)
    {
        return $query->where(function($q) use ($min, $max) {
            if ($min !== null) {
                $q->where('price', '>=', $min);
            }
            if ($max !== null) {
                $q->orWhere('price', '<=', $max);
            }
        });
    }

    public function scopePopular($query)
    {
        return $query->where(function($q) {
            $q->where('average_rating', '>=', 4.0)
              ->orWhere('total_sales', '>=', 1000);
        });
    }
}

// スコープの使用例
class ProductController extends Controller
{
    public function search(Request $request)
    {
        $products = Product::query()
            ->when($request->keyword, function($query, $keyword) {
                return $query->searchByKeyword($keyword);
            })
            ->when($request->filled(['min_price', 'max_price']), function($query) use ($request) {
                return $query->priceRange($request->min_price, $request->max_price);
            })
            ->when($request->show_popular, function($query) {
                return $query->popular();
            })
            ->paginate(20);

        return view('products.index', compact('products'));
    }
}

トレイトを使用した機能の共有

trait Searchable
{
    public function scopeMultiMatch($query, array $fields, $value)
    {
        return $query->where(function($q) use ($fields, $value) {
            foreach ($fields as $field) {
                $q->orWhere($field, 'LIKE', "%{$value}%");
            }
        });
    }

    public function scopeOrWhereInMultiple($query, array $conditions)
    {
        return $query->where(function($q) use ($conditions) {
            foreach ($conditions as $field => $values) {
                $q->orWhereIn($field, $values);
            }
        });
    }
}

// トレイトの使用例
class Product extends Model
{
    use Searchable;
}

// 検索の実装
$products = Product::multiMatch(['name', 'description', 'sku'], $keyword)
    ->orWhereInMultiple([
        'category_id' => $categories,
        'brand_id' => $brands
    ])
    ->get();

代替となるクエリビルダメソッドの比較

1. UNIONを使用した代替実装

// orWhereを使用した実装
$results = User::where('status', 'active')
    ->orWhere('role', 'admin')
    ->get();

// UNIONを使用した代替実装
$activeUsers = User::where('status', 'active');
$adminUsers = User::where('role', 'admin');

$results = $activeUsers->union($adminUsers)->get();

// より複雑な条件の場合
class UserRepository
{
    public function advancedSearch($params)
    {
        $queries = collect([]);

        // アクティブユーザーのクエリ
        if ($params['include_active']) {
            $queries->push(
                User::where('status', 'active')
                    ->select('users.*')
            );
        }

        // 管理者のクエリ
        if ($params['include_admins']) {
            $queries->push(
                User::where('role', 'admin')
                    ->select('users.*')
            );
        }

        // VIPユーザーのクエリ
        if ($params['include_vip']) {
            $queries->push(
                User::whereHas('subscription', function($query) {
                    $query->where('plan', 'vip');
                })->select('users.*')
            );
        }

        // UNIONの実行
        return $queries->reduce(function($carry, $query) {
            return $carry ? $carry->union($query) : $query;
        })->get();
    }
}

2. サブクエリを使用した代替実装

// orWhereを使用した実装
$products = Product::where('status', 'active')
    ->orWhere(function($query) {
        $query->where('stock', '>', 0)
              ->where('pre_order', true);
    })->get();

// サブクエリを使用した代替実装
$availableProductIds = Product::where('status', 'active')
    ->union(
        Product::where('stock', '>', 0)
              ->where('pre_order', true)
    )
    ->select('id');

$products = Product::whereIn('id', $availableProductIds)->get();

3. JOIN を使用した代替実装

// orWhereを使用した実装
$orders = Order::where('status', 'completed')
    ->orWhereHas('customer', function($query) {
        $query->where('tier', 'premium');
    })->get();

// JOINを使用した代替実装
$orders = Order::select('orders.*')
    ->leftJoin('customers', 'orders.customer_id', '=', 'customers.id')
    ->where(function($query) {
        $query->where('orders.status', 'completed')
              ->orWhere('customers.tier', 'premium');
    })->get();

実装方法の選択基準:

  1. パフォーマンス要件
  • データ量が多い場合はUNIONを検討
  • インデックスの効果を最大限活用できる方法を選択
  • 実行計画を確認して最適な方法を決定
  1. 保守性の考慮
  • コードの可読性
  • 再利用性の高さ
  • テストの容易さ
  1. スケーラビリティ
  • 将来の機能追加への対応
  • 検索条件の拡張性
  • パフォーマンスの維持

これらの代替手段を状況に応じて適切に選択することで、より効率的で保守性の高い実装を実現することができます。