PHP: 在嵌套的 foreach 循环中查找最后一项?

PHP: Find the last item in a nested foreach loop?

我正在使用 API 根据用户输入检索记录。

有过滤器和过滤器组将被连接起来,但是,我不希望嵌套的每个循环中的最后一个 ANDOR 连接到字符串。

我想要一个 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',
     condition: 'OR',
     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 . "'"
}

您可以尝试根据您的需要调整此解决方案。

我将在这些假设下工作:

  • 请求来自“乐高模型”构建器,因此 条件 条目始终存在,但有时并不重要(特别是当它是其组中的最后一个时).
  • 生成的代码必须是有效的 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", "condition": "OR", "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 JOINsUNIONs 和 SELECT 'code' INTO DUMPFILE '/var/www/nasty.php' 来完成,并不是所有的安装都针对它们进行了强化。

为了避免这种情况,我们使用 PDO 和 SQL 参数化。这需要分两部分发送查询,准备好的查询 like

`...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(它根本不会有任何影响,但如果它 )你可以在最后做 $filters = str_replace( '1=1 AND ', '', $filters );

(WHERE gl_code = '10-140-4700-0401') OR (WHERE ty_letter_no = 'AM123' AND gl_code = '10-140-4700-0401')

Also note that this doesn't output strictly valid SQL, just the format you requested (I'm assuming the service wants the filters in this format for their own reasons)

这是一个quick example