本文目录导读:

在PHP项目中实现组合条件筛选查询,常见的方式是根据用户选择的条件动态构建SQL的WHERE子句,下面给出几种典型的实现思路和代码示例。
基础思路(手动拼接)
核心步骤
- 接收前端传来的筛选参数(如
$_GET或$_POST)。 - 遍历参数,判断哪个条件被选中。
- 动态构建
WHERE 1=1+AND ...形式。 - 使用预处理语句(防止SQL注入)。
示例代码(MySQLi + 预处理)
<?php
// 假设筛选条件:价格区间、分类、关键词、状态
$price_min = $_GET['price_min'] ?? '';
$price_max = $_GET['price_max'] ?? '';
$category = $_GET['category'] ?? '';
$keyword = $_GET['keyword'] ?? '';
$status = $_GET['status'] ?? '';
// 基础SQL
$sql = "SELECT * FROM products WHERE 1=1";
$params = [];
$types = '';
// 价格区间
if ($price_min !== '' && $price_max !== '') {
$sql .= " AND price BETWEEN ? AND ?";
$params[] = (float)$price_min;
$params[] = (float)$price_max;
$types .= 'dd';
} elseif ($price_min !== '') {
$sql .= " AND price >= ?";
$params[] = (float)$price_min;
$types .= 'd';
} elseif ($price_max !== '') {
$sql .= " AND price <= ?";
$params[] = (float)$price_max;
$types .= 'd';
}
// 分类(精确匹配)
if (!empty($category)) {
$sql .= " AND category_id = ?";
$params[] = (int)$category;
$types .= 'i';
}
// 关键词(模糊搜索)
if (!empty($keyword)) {
$sql .= " AND (name LIKE ? OR description LIKE ?)";
$likeKeyword = '%' . $keyword . '%';
$params[] = $likeKeyword;
$params[] = $likeKeyword;
$types .= 'ss';
}
// 状态(枚举值)
if (!empty($status)) {
$sql .= " AND status = ?";
$params[] = $status;
$types .= 's';
}
// 预处理执行
$stmt = $conn->prepare($sql);
if (!empty($params)) {
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
$products = $result->fetch_all(MYSQLI_ASSOC);
注意:
WHERE 1=1的作用是保证后续的AND语法正确,也可以先判断是否有条件再决定是否加WHERE。
使用数组构建(更清晰)
适合条件较多时,用数组存储条件片段,implode。
$conditions = [];
$params = [];
$types = '';
if (!empty($category)) {
$conditions[] = "category_id = ?";
$params[] = (int)$category;
$types .= 'i';
}
if (!empty($keyword)) {
$conditions[] = "(name LIKE ? OR description LIKE ?)";
$like = '%' . $keyword . '%';
$params[] = $like;
$params[] = $like;
$types .= 'ss';
}
$where = '';
if (!empty($conditions)) {
$where = ' WHERE ' . implode(' AND ', $conditions);
}
$sql = "SELECT * FROM products" . $where;
封装成函数/类(可复用)
class ProductFilter
{
private array $conditions = [];
private array $params = [];
private string $paramTypes = '';
public function addCondition(string $sqlFragment, string $types, ...$values): void
{
$this->conditions[] = $sqlFragment;
foreach ($values as $val) {
$this->params[] = $val;
}
$this->paramTypes .= $types;
}
public function getSQL(string $baseSQL): string
{
if (empty($this->conditions)) {
return $baseSQL;
}
return $baseSQL . ' WHERE ' . implode(' AND ', $this->conditions);
}
public function getParams(): array
{
return [$this->paramTypes, ...$this->params];
}
// 用法示例
public function buildFromRequest(array $request): void
{
if (!empty($request['category'])) {
$this->addCondition('category_id = ?', 'i', (int)$request['category']);
}
if (!empty($request['keyword'])) {
$like = '%' . $request['keyword'] . '%';
$this->addCondition('(name LIKE ? OR description LIKE ?)', 'ss', $like, $like);
}
// 其他条件...
}
}
// 使用
$filter = new ProductFilter();
$filter->buildFromRequest($_GET);
$sql = $filter->getSQL("SELECT * FROM products");
list($types, ...$params) = $filter->getParams();
$stmt = $conn->prepare($sql);
$stmt->bind_param($types, ...$params);
处理常见特殊条件
多选(如多个分类ID)
$category_ids = $_GET['category_ids'] ?? []; // 数组
if (!empty($category_ids)) {
// 生成占位符 ? count($category_ids) 个
$placeholders = implode(',', array_fill(0, count($category_ids), '?'));
$conditions[] = "category_id IN ($placeholders)";
foreach ($category_ids as $id) {
$params[] = (int)$id;
$types .= 'i';
}
}
日期范围
if (!empty($date_from) && !empty($date_to)) {
$conditions[] = "created_at BETWEEN ? AND ?";
$params[] = $date_from;
$params[] = $date_to;
$types .= 'ss';
}
排序(非筛选,但常结合使用)
$order = $_GET['order'] ?? 'id';
$sort = strtoupper($_GET['sort'] ?? 'DESC');
// 白名单校验
$allowedOrders = ['id', 'price', 'created_at'];
$allowedSorts = ['ASC', 'DESC'];
if (in_array($order, $allowedOrders) && in_array($sort, $allowedSorts)) {
$sql .= " ORDER BY $order $sort";
} else {
$sql .= " ORDER BY id DESC"; // 默认
}
安全建议
| 风险点 | 措施 |
|---|---|
| SQL注入 | 必须使用预处理语句(如 bind_param 或 PDO 的 prepare + execute) |
| 参数未过滤 | 类型转换(intval, floatval)、htmlspecialchars(输出时) |
| 排序字段注入 | 白名单校验,绝不直接拼接用户输入的字段名 |
| 模糊搜索性能 | 考虑 全文索引 或限制关键词长度(如 min(100, strlen($keyword))) |
使用框架(推荐)
如果项目使用 Laravel / ThinkPHP / Symfony 等,框架自带查询构建器更加安全简洁:
Laravel 示例
$products = Product::query()
->when($request->category, fn($q, $v) => $q->where('category_id', $v))
->when($request->keyword, fn($q, $v) => $q->where(function($q2) use ($v) {
$q2->where('name', 'like', "%$v%")
->orWhere('description', 'like', "%$v%");
}))
->when($request->price_min, fn($q, $v) => $q->where('price', '>=', $v))
->when($request->price_max, fn($q, $v) => $q->where('price', '<=', $v))
->orderBy($request->order ?? 'id', $request->sort ?? 'desc')
->paginate(15);
- 原生写法:手动构建SQL + 预处理参数。
- 封装写法:条件数组、函数/类封装。
- 框架写法:利用链式调用,最安全简洁。
无论哪种方式,核心都是根据用户输入动态拼接 WHERE 子句并用预处理绑定参数。