Web開発でフィルター機能を実装する際、URLパラメータで複数の値を渡したいケースはよくあります。

例:複数の物件IDで絞り込み検索
https://example.com/properties?id=muromi,meinohama&status=available

しかし、配列形式のURLパラメータを実装したら真っ白なエラー画面になったという経験はありませんか?

本記事では、URLパラメータで配列を渡す3つの方法と、それぞれのメリット・デメリット、そしてWAF(Web Application Firewall)によるブロック問題とその解決策を実例を交えて解説します。


    URLパラメータで配列を渡す3つの方法

    1. カンマ区切り形式(推奨)

    https://example.com/search?id=1,2,3&category=A,B
    

    メリット:

    • ✅ URLが短く、可読性が高い
    • ✅ WAFにブロックされにくい
    • ✅ SEOフレンドリー(URLが短い)
    • ✅ シンプルで直感的

    デメリット:

    • ❌ サーバー側で explode(',') による分割処理が必要
    • ❌ 値にカンマが含まれる場合はエスケープが必要

    PHP実装例:

    // カンマ区切りのパラメータを配列に変換
    $filter_ids = [];
    if (!empty($_GET['id'])) {
      $filter_ids = array_map('trim', explode(',', $_GET['id']));
      $filter_ids = array_filter($filter_ids); // 空要素を除去
    }
    
    // 結果: ['1', '2', '3']
    

    JavaScript実装例:

    // 配列をカンマ区切りのURLに変換
    const ids = ['muromi', 'meinohama'];
    const params = new URLSearchParams();
    params.set('id', ids.join(',')); // カンマで結合
    
    // 結果: ?id=muromi,meinohama
    window.location.href = `?${params.toString()}`;
    

    2. 繰り返し形式(RFC準拠)

    https://example.com/search?id=1&id=2&id=3
    

    メリット:

    • ✅ RFC 3986に準拠した標準的な方法
    • ✅ サーバー側で自動的に配列として扱われる
    • ✅ 多くのフレームワークでサポート
    • ✅ WAFにブロックされにくい

    デメリット:

    • ❌ URLが長くなる
    • ❌ 可読性が低い

    PHP実装例:

    // PHPは自動的に配列として受け取る
    $filter_ids = $_GET['id'] ?? [];
    
    // 結果: ['1', '2', '3']
    

    JavaScript実装例:

    const ids = ['muromi', 'meinohama'];
    const params = new URLSearchParams();
    ids.forEach(id => params.append('id', id)); // 同じキーを繰り返し追加
    
    // 結果: ?id=muromi&id=meinohama
    window.location.href = `?${params.toString()}`;
    

    3. ブラケット形式(非推奨)

    https://example.com/search?id[]=1&id[]=2&id[]=3
    

    メリット:

    • ✅ 配列であることが明示的
    • ✅ PHPなど一部の言語で標準的

    デメリット:

    • WAFでブロックされる可能性が高い
    • ❌ RFC 3986で [] が予約文字として定義されている
    • ❌ モバイルアプリ(iOS/Android)との連携で問題が発生しやすい
    • ❌ URLエンコード後は %5B%5D となり可読性が低い

    問題例:

    https://example.com/search?id%5B%5D=1&id%5B%5D=2
    → 真っ白なエラー画面(403 Forbidden)
    

    ⚠️ WAFでブロックされる問題

    問題の原因

    レンタルサーバー(カラフルボックス、エックスサーバー、ロリポップなど)の多くは、デフォルトでWAF(Web Application Firewall)が有効になっています。

    WAFは、[] を含むURLパラメータをSQLインジェクションやXSS攻撃のパターンとして誤検知し、ブロックすることがあります。

    ブロックされた時の症状

    • 真っ白な画面が表示される
    • 403 Forbidden エラーが表示される
    • サーバーログに「ModSecurity」などのエラーが記録される

    解決方法

    方法1: カンマ区切り形式または繰り返し形式に変更(推奨)

    // ❌ ブロックされる
    ?id[]=muromi&id[]=meinohama
    
    // ✅ ブロックされない
    ?id=muromi,meinohama  // カンマ区切り
    
    // ✅ ブロックされない
    ?id=muromi&id=meinohama  // 繰り返し
    

    方法2: WAFのルールを調整(非推奨)

    サーバーの管理画面でWAFの特定ルールを無効化できますが、セキュリティリスクが高まるため推奨しません。


    実務での推奨パターン

    おすすめ度ランキング

    順位 方式 用途 理由
    🥇 カンマ区切り 一般的なWebアプリ URLが短く、WAF対策も不要
    🥈 繰り返し API設計、標準準拠 RFC準拠で多くのライブラリ対応
    🥉 ブラケット (非推奨) WAF問題とモバイル対応の問題

    選択基準

    カンマ区切りを選ぶべきケース:

    • レンタルサーバーで運用する
    • URLの可読性・SEOを重視する
    • 社内システムなど限定的な用途

    繰り返し形式を選ぶべきケース:

    • 外部APIとして公開する
    • RFC準拠を重視する
    • 多くのクライアントライブラリとの互換性が必要

    完全な実装例(PHPとJavaScript)

    PHP側の実装

    <?php
    /**
     * カンマ区切り形式のURLパラメータを処理
     * 例: ?id=muromi,meinohama&status=sent,completed
     */
    
    // 物件ID(カンマ区切り → 配列に変換)
    $filter_ids = [];
    if (!empty($_GET['id'])) {
      $filter_ids = array_map('trim', explode(',', $_GET['id']));
      $filter_ids = array_filter($filter_ids); // 空要素を除去
    }
    
    // ステータス(カンマ区切り → 配列に変換)
    $filter_statuses = [];
    if (!empty($_GET['status'])) {
      $filter_statuses = array_map('trim', explode(',', $_GET['status']));
      $filter_statuses = array_filter($filter_statuses);
    }
    
    // データベースクエリでの使用例
    if (!empty($filter_ids)) {
      $placeholders = implode(',', array_fill(0, count($filter_ids), '?'));
      $sql = "SELECT * FROM properties WHERE id IN ($placeholders)";
      // 以下、プリペアドステートメントで実行
    }
    ?>
    

    JavaScript側の実装

    /**
     * フォーム送信時にカンマ区切り形式のURLを生成
     */
    document.querySelector('form').addEventListener('submit', function(e) {
      e.preventDefault();
      
      const form = this;
      const params = new URLSearchParams();
      
      // チェックボックスで選択された物件ID
      const selectedIds = Array.from(form.querySelectorAll('input[name="id[]"]:checked'))
        .map(input => input.value);
      
      if (selectedIds.length > 0) {
        params.set('id', selectedIds.join(',')); // カンマで結合
      }
      
      // チェックボックスで選択されたステータス
      const selectedStatuses = Array.from(form.querySelectorAll('input[name="status[]"]:checked'))
        .map(input => input.value);
      
      if (selectedStatuses.length > 0) {
        params.set('status', selectedStatuses.join(','));
      }
      
      // カンマ区切り形式のURLにリダイレクト
      const url = window.location.pathname + '?' + params.toString();
      window.location.href = url;
      
      // 結果: ?id=muromi,meinohama&status=sent,completed
    });
    

    各言語・フレームワークでの対応状況

    PHP

    • ✅ カンマ区切り: explode() で対応可能
    • ✅ 繰り返し: 自動的に配列として受け取る
    • ⚠️ ブラケット: デフォルトで対応だがWAF問題

    Node.js / Express

    // 繰り返し形式
    app.get('/search', (req, res) => {
      const ids = req.query.id; // 配列またはスカラー値
      const idsArray = Array.isArray(ids) ? ids : [ids];
    });
    

    Ruby on Rails

    # 繰り返し形式(自動的に配列化)
    params[:id] # => ["muromi", "meinohama"]
    

    Python / Flask

    # 繰り返し形式
    from flask import request
    
    @app.route('/search')
    def search():
        ids = request.args.getlist('id')  # ['muromi', 'meinohama']
    

    モバイルアプリとの連携

    iOS(Swift / Alamofire)

    // カンマ区切り形式を推奨
    let parameters: [String: String] = [
        "id": "muromi,meinohama",
        "status": "sent"
    ]
    
    AF.request(url, method: .get, parameters: parameters)
    

    注意: Alamofireのデフォルト設定では配列にブラケットが付くため、カンマ区切り形式が推奨されます。

    Android(Kotlin / Retrofit)

    // 繰り返し形式が一般的
    @GET("search")
    suspend fun search(
        @Query("id") ids: List<String>
    ): Response<SearchResult>
    
    // 呼び出し
    api.search(listOf("muromi", "meinohama"))
    

    パフォーマンスへの影響

    サーバー側の処理負荷

    方式 処理内容 負荷
    カンマ区切り explode() による文字列分割
    繰り返し PHPが自動処理 最低
    ブラケット PHPが自動処理 最低

    結論: パフォーマンス面では大差なし。可読性とWAF対策を優先すべき。


    セキュリティ上の注意点

    1. SQLインジェクション対策

    // ❌ 危険: 直接SQLに結合
    $sql = "SELECT * FROM properties WHERE id IN (" . implode(',', $filter_ids) . ")";
    
    // ✅ 安全: プリペアドステートメントを使用
    $placeholders = implode(',', array_fill(0, count($filter_ids), '?'));
    $stmt = $pdo->prepare("SELECT * FROM properties WHERE id IN ($placeholders)");
    $stmt->execute($filter_ids);
    

    2. 入力値の検証

    // 数値のみを許可
    $filter_ids = array_filter($filter_ids, function($id) {
      return ctype_digit($id);
    });
    
    // 許可リストで検証
    $allowed_statuses = ['sent', 'pending', 'completed'];
    $filter_statuses = array_filter($filter_statuses, function($status) use ($allowed_statuses) {
      return in_array($status, $allowed_statuses);
    });
    

    まとめ

    1. カンマ区切り形式が実用的

      • URLが短く、WAF問題もない
      • レンタルサーバーでも安全に動作
    2. 繰り返し形式はAPI向け

      • RFC準拠で標準的
      • 外部公開APIなら推奨
    3. ブラケット形式は避けるべき

      • WAFでブロックされるリスク
      • モバイルアプリとの連携で問題

    チェックリスト

    • レンタルサーバーでWAFが有効か確認
    • URLの可読性を考慮
    • モバイルアプリとの連携が必要か確認
    • サーバー側でのバリデーション実装
    • SQLインジェクション対策の実施

    参考リンク


    関連記事

    PHPでCSRF対策を実装する方法|CSRFトークンの生成・検証・エラー・デバッグ方法まで徹底解説 PHPでのCSRF(クロスサイトリクエストフォージェリ)対策を実装する方法を徹底解説。トークンの生成・検証・セッション管理から、よくあるエラーのデバッグ方法まで、実例付きで分かりやすく説明します。  続きを読む