【问题标题】:PHP: Find the last item in a nested foreach loop?PHP:在嵌套的 foreach 循环中查找最后一项?
【发布时间】:2021-04-05 16:28:51
【问题描述】:

我正在使用 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')"

【问题讨论】:

  • 你获取的代码应该是SQL吧?它有几个问题。你能举一个你得到的请求的例子吗?即“{“过滤器”:{“key1”:[{“code”:“ty_letter_no”,“tag”:“AU”}],“key2”:[...]},“条件”:{“key1 ": { "condition": "or" } }",应该输出什么?
  • 我添加了一个请求示例。
  • 我不太明白为什么您有一个带有“OR”的 groupCondition,并且 还有在过滤器内有一个“条件”。坦率地说,虽然您的要求很简单(我自己编写了几个这样的过滤器),但这个请求对象的结构却不是。最终代码是有效的 SQL,还是其他什么?在您的示例请求中,两个 OR 似乎都丢失了。这是预期的结果吗?
  • ????:“看起来您正在编写自己的 ORM。您是否考虑过使用已经编写、测试和广泛支持的 ORM,例如 AtlasDoctrine 或 @ 987654323@?”
  • 警告:使用mysqli 时,您应该使用parameterized queriesbind_param 将任何数据添加到您的查询中。 请勿使用字符串插值或连接来完成此操作,因为您创建了一个严重的SQL injection bug切勿$_POST$_GET任何类型的数据直接放入查询中,如果有人试图利用您的错误,这可能会非常有害。

标签: php foreach nested-loops


【解决方案1】:

为了严格回答您的问题,您能否通过检查当前迭代(在您的情况下为键)与数组的长度(count())来查看您是否正在处理最后一次迭代。您可以通过确保if( $iteration < $length - 1 ) 避免在该迭代中执行某些操作,然后您不在最后一次迭代中。如果您想检查自己是否在最后,if( $iteration == $length - 1 )

为了解决更大范围的问题:

当以编程方式构建查询时,我经常发现使用WHERE 1=1 开始更容易,因为它允许您使用AND 作为粘合剂来implode() 一个数组(有些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')

另请注意,这不会输出严格有效的 SQL,只是您请求的格式(我假设服务出于自身原因需要这种格式的过滤器)

这是quick example

【讨论】:

    【解决方案2】:

    我将在这些假设下工作:

    • 请求来自“乐高模型”构建器,因此 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", "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 参数化。这需要分两部分发送查询,prepared query like

    `...AND gl_code = :value1`
    

    和一个包含...':value1' =&gt; '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.
    }
    

    【讨论】:

      【解决方案3】:

      只是一个想法:您总是可以了解当前迭代是否是第一次(通过使用标志)。

      这样,你可以:

      • 跳过第一次迭代
      • 在每次迭代时保存当前项,以便在下一次迭代中使用
      • 在第 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 . "'"
      }
      

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

      【讨论】:

        猜你喜欢
        • 2015-07-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-05
        • 1970-01-01
        • 1970-01-01
        • 2017-11-06
        相关资源
        最近更新 更多