请注意,您无条件输出<optgroup>
每次迭代;因此,如果您不想每次都输出,则需要(至少)使输出有条件。在许多情况下,这还不够,因为连续的行可能位于不同的组中;相反,您需要在 PHP 中对结果进行分组,然后迭代结果。在这里,经过一些修改ORDER BY
子句将确保每个类别中的项目按顺序处理。当前的语句还不够,因为类别是在单独的语句中检索的,这不仅阻止了ORDER BY
使类别中的项目连续但效率低下,因为它发出的请求超出了必要的数量。
这两个语句可以用一个组合起来JOIN
:
SELECT cp.product AS name,
cp.description,
cp.tax,
cp.new_price_rate,
cp.new_total_rate,
ap.category
FROM customer_product AS cp
LEFT JOIN addproducts AS ap ON cp.product = ap.name
WHERE customer LIKE ? -- NB: parameter for prepared statement
ORDER BY ap.category
如果 customer_product.product 和 ap.name 之间存在 1:N 关系,则需要修改查询,以便为每个产品仅返回一行customer_product
行(一种简单的方法是按所有行进行分组cp
rows).
确定何时输出<optgroup>
,代码需要检测类别何时发生变化。通过存储当前类别并将其与新行的类别进行比较,可以轻松完成此操作。
一般来说,您必须考虑第一次、中间和最后一次迭代时发生的情况。
第一次迭代很重要,因为您想要强制 optgroup 的输出。只要存储的类别永远不会等于数据库中的类别,这本质上是自动的。
另外,没有</optgroup>
关闭标签应在第一次迭代时输出。第一次跳过此步骤的一个简单方法是使用一个变量来保存要输出的标签,该变量被初始化为仅打开标签,然后在第一次添加关闭标签<optgroup>
是输出。另一个是有一个标志记录是否有一个 optgroup 需要关闭(初始化为FALSE
并设置为TRUE
when <optgroup>
是输出),并且仅当标志为真时才输出关闭标记。
最后一次迭代之后,最后一次<optgroup>
必须关闭。假设 1 个类别中至少有 1 个产品,您应该能够在循环后无条件输出关闭标签。
请注意,问题中的示例代码混合了许多不同类型的任务,主要是数据库访问和输出。这违反了关注点分离 https://en.wikipedia.org/wiki/Separation_of_concerns。相反,每个都应该放置在单独的模块中。实现这一点的确切方法远远超出了本问答的范围,但下面的示例代码中使用了一种简化的方法。
请务必使用以下方式对任何非 HTML 字符串进行编码htmlspecialchars https://php.net/htmlspecialchars,既可以防止注入,也可以防止 HTML 损坏。
<?php
// initially, there's no category
$category = NULL;
// the category tag; will be updated to close the previous element the first time it's output
$catTag = '<optgroup ';
// attributes for each <option> element
$attrs = [
'value' => 'name',
'data-new_price_rate' => 'new_total_rate',
'data-description' => 'description',
'data-tax' => 'tax',
'data-PriceRate_NoDiscount' => 'new_price_rate',
];
?>
<select>
<option disabled>Click to see products</option><!-- Note: this smells a bit. -->
<?php
// note there's no sign of DB access; the products could come from anywhere
foreach ($customerProducts->fetch($comid) as $product) {
// The core of the answer: output a new optgroup only then when the category changes
if ($category != $product['category']) {
echo $catTag, 'label="', htmlspecialchars($product['category']), "\">\n";
// from now on, close the previous element when there's a new optgroup
$catTag = "</optgroup>\n<optgroup ";
}
// output the current product as an option element
?>
<option<?php foreach ($attrs as $attr => $prop) {
echo ' ', $attr, '="', htmlspecialchars($product[$prop]), '"';
} ?>><?= htmlspecialchars($product['name']) ?></option>
<?php
$category = $product['category'];
}
?>
</optgroup>
</select>
该示例假设每个产品都属于 1 个类别,因此$product['category']
不为空。但是,如果任何产品不属于某个类别,则它应该仍然有效,除非结果中的每个产品都不属于任何类别,在这种情况下,将不会有<optgroup>
s 和决赛</optgroup>
不会关闭任何内容,从而产生无效的 HTML。
以下(未经测试)示例仅用于将数据库访问与 HTML 生成分开。在其他地方搜索有关主题的信息,例如DALs https://en.wikipedia.org/wiki/Data_access_layer, 准备好的陈述 https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php,以及可穿越 https://php.net/Traversable接口(由mysqli_result https://php.net/mysqli_result, 尽管PDO https://php.net/PDO具有更好的支持,包括允许您将结果类型设置为关联数组之外的其他类型PDOStatement->setAttribute https://php.net/PDOStatement.setAttribute).
class CustomerProducts {
static $statements = [
'read' => 'SELECT […]',
];
function __construct($db) {
$this->db = $db;
$this->read = $db->prepare(self::$statements[read]);
}
function fetch($id) {
$this->read->bind_param('i', $id);
if ($this->read->execute()) {
return $this->read->get_result();
} else {
// handle failure with e.g. an exception
throw …;
}
}
}