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);
});
まとめ
カンマ区切り形式が実用的
- URLが短く、WAF問題もない
- レンタルサーバーでも安全に動作
繰り返し形式はAPI向け
- RFC準拠で標準的
- 外部公開APIなら推奨
ブラケット形式は避けるべき
- WAFでブロックされるリスク
- モバイルアプリとの連携で問題
チェックリスト
- レンタルサーバーでWAFが有効か確認
- URLの可読性を考慮
- モバイルアプリとの連携が必要か確認
- サーバー側でのバリデーション実装
- SQLインジェクション対策の実施
参考リンク
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
- PHP: $_GET - Manual
- URLSearchParams - Web APIs | MDN
関連記事
PHPでCSRF対策を実装する方法|CSRFトークンの生成・検証・エラー・デバッグ方法まで徹底解説

