如何解决PHP:在嵌套的 foreach 循环中查找最后一项?
我正在使用 API 来根据用户输入检索记录。
有过滤器和过滤器组将被连接,但是,我不希望每个循环嵌套中的最后一个 AND
或 OR
连接到字符串。
我想要一个 if 语句来查找嵌套 foreach 循环中的最后一项并将不同的字符串连接到末尾,如下所示:
if($i == $lastItem) {
$conditions .= 'WHERE ' . $type . ' = '. "'". $tag . "'";
}
else {
$conditions .= 'WHERE ' . $type . ' = '. "'". $tag . "'" . ' ' . $condition . " ";
}
使用 PHP 为每个循环查找嵌套的最后一项的最佳实践是什么?
这是参考代码:
$conditions = "";
$filters = "";
foreach($request->filters as $key=>$filter) {
foreach($filter as $item) {
if($item['tag']) {
$type = $item['code'];
$tag = $item['tag'];
$condition = $item['condition'];
$conditions .= 'WHERE ' . $type . ' = '. "'". $tag . "'" . ' ' . $condition . " ";
}
}
$groupCondition = $request->conditions[$key]['condition'];
$filters .= '('.$conditions.') ' . $groupCondition . " ";
}
这里是一个请求的例子,两个对象在foreach循环中组合:
filter: {
0 {
0 {
code: 'gl_code',condition: 'OR',tag: '10-140-4700-0401'
}
1 {
0 {
code: 'ty_letter_no',condition: 'AND',tag: 'AM123'
},1 {
code: 'gl_code',tag: '10-140-4700-0401'
}
}
groupConditions: {
0 {
condition: 'OR'
}
1 {
condition: 'AND'
}
}
这是当前输出的示例:
"(WHERE ty_letter_no = 'AM123' AND )
OR
(WHERE ty_letter_no = 'AM123' AND WHERE solicit_code = '19-10NL' AND WHERE ty_letter_no = 'AU' AND )
AND
(WHERE ty_letter_no = 'AM123' AND WHERE solicit_code = '19-10NL' AND WHERE ty_letter_no = 'AU' AND WHERE solicit_code = '19-04HRGOLF' AND )
AND "
我希望它输出:
"(WHERE ty_letter_no = 'AM123')
OR
(WHERE ty_letter_no = 'AM123' AND WHERE solicit_code = '19-10NL' AND WHERE ty_letter_no = 'AU')
AND
(WHERE ty_letter_no = 'AM123' AND WHERE solicit_code = '19-10NL' AND WHERE ty_letter_no = 'AU' AND WHERE solicit_code = '19-04HRGOLF')"
解决方法
只是一个想法:您总是可以了解当前迭代是否是第一个(通过使用标志)。
通过这种方式,您可以:
- 跳过第一次迭代
- 在每次迭代时保存当前项目,以供下一次迭代使用
- 在第 Nth 次迭代时更新查询,附加与 (N-1)th 次迭代相关的信息
- 在循环外附加与上次迭代相关的信息,应用您需要的修改
类似的东西
$conditions = "";
$filters = "";
$isFirst = true;
foreach($request->filters as $key=>$filter) {
foreach($filter as $item) {
if($isFirst)
{
$isFirst = false;
}
else
{ if($previousItem['tag']) {
$type = $previousItem['code'];
$tag = $previousItem['tag'];
$condition = $previousItem['condition'];
$conditions .= 'WHERE ' . $type . ' = '. "'". $tag . "'" . ' ' . $condition . " ";
}
}
$previousItem = $item;
}
$groupCondition = $request->conditions[$key]['condition'];
$filters .= '('.$conditions.') ' . $groupCondition . " ";
}
if(isset($previousItem))
{
$type = $previousItem['code'];
$tag = $previousItem['tag'];
$conditions .= 'WHERE ' . $type . ' = '. "'". $tag . "'"
}
您可以尝试根据您的需要调整此解决方案。
,我将在这些假设下工作:
- 请求来自“LEGO 模型”构建器,因此 condition 条目始终存在,但有时并不重要(特别是当它是其组的最后一个时)。
- 生成的代码必须是有效的 SQL 条件。
- 字符串值不需要转义,并且已针对 SQL 注入进行了验证。
如果是这样,那么您可以使用有限状态机来实现您的结果:
$completeCondition = '';
$groupjoin = '';
foreach ($request->filter as $index => $conditions) {
$conditionjoin = '';
$partialCondition = '';
foreach ($conditions as $triplet) {
$partialCondition .= "{$conditionjoin}{$triplet->code} = '{$triplet->tag}'";
$conditionjoin = " {$triplet->condition} ";
}
$completeCondition .= "{$groupjoin}({$partialCondition})";
$groupjoin = " {$request->groupConditions[$index]->condition} ";
}
if (!empty($completeCondition)) {
$completeCondition = " WHERE {$completeCondition}";
}
使用此版本的请求,
$request = json_decode('{
"filter": [
[
{ "code": "gl_code","condition": "OR","tag": "10-140-4700-0401" }
],[
{ "code": "ty_letter_no","condition": "AND","tag": "AM123" },{ "code": "gl_code","tag": "10-140-4700-0401" }
]
],"groupConditions": [ { "condition": "OR" },{ "condition": "AND" } ]
}');
结果如下,有效的 SQL:
WHERE (gl_code = '10-140-4700-0401')
OR (ty_letter_no = 'AM123' AND gl_code = '10-140-4700-0401')
(如果目标语言不是 SQL,当然可以稍微更改代码)。
更精细的结果(PDO 支持)
通常,您不希望在您的 SQL 代码中按原样包含请求字符串,因为这允许用户随意更改您将执行的 SQL 代码。例如,如果我要发送一个
标签'||SLEEP(60)||'
上面的代码很乐意将其编码为 gl_code = ''||SLEEP(60)||''
,这是一个有效的 SQL 请求,将被执行,暂停您的线程 60 秒。如果我知道您正在使用 MySQL,我可以使用 LOCK()
函数执行一些技巧并尝试耗尽内部元数据内存。如果您真的倒霉,并且复合查询没有被禁用(它们通常是!),那么我非常担心我可以拥有您的 SQL 服务器。即使是这样,也可以使用 LEFT JOINs
、UNION
和 SELECT 'code' INTO DUMPFILE '/var/www/nasty.php'
来完成一些肮脏的技巧,而且并非所有安装都针对所有这些技巧进行了加固。
为了避免这种情况,我们使用 PDO 和 SQL 参数化。这需要分两部分发送查询,一个准备好的查询,如
`...AND gl_code = :value1`
和一个包含...':value1' => 'AL-1234-56'...
的绑定列表。
$completeCondition = '';
$groupjoin = '';
$bindingList = array();
foreach ($request->filter as $index => $conditions) {
$conditionjoin = '';
$partialCondition = '';
foreach ($conditions as $triplet) {
$bind = ':b' . count($bindingList);
$partialCondition .= "{$conditionjoin}{$triplet->code} = {$bind}";
$conditionjoin = " {$triplet->condition} ";
$bindingList[$bind] = $triplet->tag;
}
$completeCondition .= "{$groupjoin}({$partialCondition})";
$groupjoin = " {$request->groupConditions[$index]->condition} ";
}
if (!empty($completeCondition)) {
$completeCondition = " WHERE {$completeCondition}";
}
// Now we could safely do (supposing $pdo is my PDO DB object)
$stmt = $pdo->prepare($completeCondition);
$stmt->execute($bindingList);
while ($row = $stmt->fetch()) {
// Do something with the row.
}
,
为了严格回答您的问题,您能否通过检查当前迭代(在您的情况下,是键)与数组的长度 (count()
) 来确定您是否正在处理最后一次迭代。您可以通过确保 if( $iteration < $length - 1 )
,然后您不在最后一次迭代中来避免在该迭代中执行某些操作。如果您想检查自己是否是,请if( $iteration == $length - 1 )
。
为了解决更大范围的问题:
在以编程方式构建查询时,我经常发现使用 WHERE 1=1
开始更容易,因为它允许您使用 implode()
作为粘合剂 AND
数组 (并且某些 DBMS 甚至不会解析 1=1
)。
$filters = "";
$length = count($request->filters);
foreach($request->filters as $key => $filter) {
$conditions = array( "WHERE 1=1" );
foreach($filter as $item){
$tag = $item['tag'];
$type = $item['code'];
$condition = $item['condition'];
$conditions[] = "{$type} = '{$tag}'";
}
// Build the conditions
$filters .= sprintf( '(%s)',implode(' AND ',$conditions ) );
// If this isn't the last item,add the group conditions
$filters .= ( $key < $length-1 ) ? " {$request->conditions[$key]['condition']} " : '';
}
最后一行检查以确保它不是最后一项(通过在附加最后一个条件之前确保当前索引小于 $length-1
。使用上面的,你最终会得到一个过滤器:
(WHERE 1=1 AND gl_code = '10-140-4700-0401') OR (WHERE 1=1 AND ty_letter_no = 'AM123' AND gl_code = '10-140-4700-0401')
如果您要发送的服务不喜欢 1=1
(它根本不应该有任何影响,但如果它确实),您可以执行 {{ 1}} 结尾。
$filters = str_replace( '1=1 AND ','',$filters );
另请注意,这不会输出严格有效的 SQL,只是您请求的格式(我假设服务出于自身原因需要这种格式的过滤器)
这是一个quick example
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。