PHP: 在嵌套的 foreach 循环中查找最后一项?
PHP: Find the last item in a nested foreach loop?
我正在使用 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',
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 JOINs
、UNION
s 和 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
我正在使用 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',
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 JOINs
、UNION
s 和 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