如何使用PHP解析含嵌套括号的自定义简单搜索查询?
嵌套条件搜索查询的解析方案
核心思路
嵌套括号的本质是层级结构,用递归+栈的方式处理最直接:先定位所有匹配的括号对,递归解析括号内的子表达式,再将子结果整合到外层表达式中,最终得到你需要的嵌套数组结构。
具体实现步骤
1. 解析嵌套括号(递归处理层级)
通过栈跟踪括号的开启与闭合,遇到闭合括号时提取内部子表达式,递归解析后替换回原位置:
function parseNestedQuery($query) { $stack = []; $resultParts = []; $currentSegment = ''; $queryLen = strlen($query); for ($i = 0; $i < $queryLen; $i++) { $char = $query[$i]; if ($char === '(') { // 先把当前积累的非括号内容存入结果 if (!empty(trim($currentSegment))) { $resultParts[] = trim($currentSegment); $currentSegment = ''; } array_push($stack, count($resultParts)); // 记录括号起始位置 $resultParts[] = '('; } elseif ($char === ')') { if (!empty(trim($currentSegment))) { $resultParts[] = trim($currentSegment); $currentSegment = ''; } $startIdx = array_pop($stack); // 提取括号内的所有元素,递归解析子表达式 $subExprParts = array_slice($resultParts, $startIdx + 1); array_splice($resultParts, $startIdx, count($subExprParts) + 1); $resultParts[] = parseNestedQuery(implode(' ', $subExprParts)); } else { $currentSegment .= $char; } } // 处理剩余的非括号内容 if (!empty(trim($currentSegment))) { $resultParts[] = trim($currentSegment); } // 解析当前层级的平级表达式(拆分条件与运算符) return parseFlatExpression($resultParts); }
2. 解析平级表达式(拆分条件与运算符)
将无嵌套的表达式拆分为你需要的Condition+Operator结构:
function parseFlatExpression($parts) { $expression = []; $currentCondition = ''; $lastOperator = null; foreach ($parts as $part) { $upperPart = strtoupper($part); if ($upperPart === 'AND' || $upperPart === 'OR') { if (!empty(trim($currentCondition))) { $expression[] = [ 'Condition' => trim($currentCondition), 'Operator' => $upperPart ]; $currentCondition = ''; } $lastOperator = $upperPart; } else { if (is_array($part)) { // 子表达式结果直接加入,同时处理前置运算符 if (!empty(trim($currentCondition))) { $expression[] = [ 'Condition' => trim($currentCondition), 'Operator' => $lastOperator ]; $currentCondition = ''; } $expression[] = $part; } else { $currentCondition .= ($currentCondition ? ' ' : '') . $part; } } } // 加入最后一个无后续运算符的条件 if (!empty(trim($currentCondition))) { $expression[] = ['Condition' => trim($currentCondition)]; } return $expression; }
3. 安全求值(替代eval避免注入风险)
直接使用eval处理用户输入存在严重安全漏洞,建议用白名单验证+规则匹配的方式求值,同时实现短路逻辑:
// 单个条件求值 function evaluateSingleCondition($condition, $userPassword, $userDate) { $condition = trim($condition); // 处理DATE BEFORE条件 if (preg_match('/^DATE BEFORE "([^"]+)"$/', $condition, $matches)) { $targetDate = DateTime::createFromFormat('d-m-Y H:i', $matches[1]); $userDateTime = new DateTime($userDate); return $userDateTime < $targetDate; } // 处理DATE AFTER条件 elseif (preg_match('/^DATE AFTER "([^"]+)"$/', $condition, $matches)) { $targetDate = DateTime::createFromFormat('d-m-Y H:i', $matches[1]); $userDateTime = new DateTime($userDate); return $userDateTime > $targetDate; } // 处理PASSWORD IS条件 elseif (preg_match('/^PASSWORD IS "([^"]+)"$/', $condition, $matches)) { return $userPassword === $matches[1]; } // 处理PASSWORD IS NOT条件 elseif (preg_match('/^PASSWORD IS NOT "([^"]+)"$/', $condition, $matches)) { return $userPassword !== $matches[1]; } return false; } // 递归求值整个表达式(支持短路逻辑) function evaluateExpression($expression, $userPassword, $userDate) { $finalResult = null; $currentOperator = null; foreach ($expression as $item) { if (is_array($item)) { $currentValue = isset($item['Condition']) ? evaluateSingleCondition($item['Condition'], $userPassword, $userDate) : evaluateExpression($item, $userPassword, $userDate); if ($finalResult === null) { $finalResult = $currentValue; } else { if ($currentOperator === 'AND') { if (!$currentValue) return false; // AND短路:假则直接返回 $finalResult = $finalResult && $currentValue; } elseif ($currentOperator === 'OR') { if ($currentValue) return true; // OR短路:真则直接返回 $finalResult = $finalResult || $currentValue; } } $currentOperator = $item['Operator'] ?? null; } } return $finalResult ?? false; }
使用示例
// 测试查询字符串 $query = '((PASSWORD IS "welcome" OR PASSWORD IS "1234") AND DATE BEFORE "01-11-2022") OR PASSWORD = "admin"'; // 解析为嵌套数组结构 $parsed = parseNestedQuery($query); // 模拟用户参数 $userPassword = '1234'; $userDate = '01-10-2022 10:00'; // 求值 $result = evaluateExpression($parsed, $userPassword, $userDate); var_dump($result); // 输出bool(true)
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

