
本文详解如何在 php 中基于 html 表单提交的用户选择(尺寸、配料、饼底类型),准确计算并展示披萨订单总价,同时安全写入日志文件,附完整面向对象实现与关键注意事项。
本文详解如何在 php 中基于 html 表单提交的用户选择(尺寸、配料、饼底类型),准确计算并展示披萨订单总价,同时安全写入日志文件,附完整面向对象实现与关键注意事项。
在电商类表单或定制化服务场景中,根据用户多维度选择实时计算价格是核心功能。本文以披萨订单为例,提供一个健壮、可维护、符合现代 PHP 实践的解决方案——使用面向对象方式封装价格逻辑,避免原始代码中硬编码、类型混淆与文件操作风险。
核心设计思路
我们摒弃过程式脚本的脆弱结构,构建 Pizza 类统一管理:
- 配置常量化:尺寸、配料、饼底选项及对应价格全部定义为 const,便于维护与国际化扩展;
- 数据标准化:将表单传入的字符串值(如 "Large"、"DeepDish")映射为整型索引(0, 2),消除大小写/空格敏感问题;
- 价格解耦计算:calculatePizza() 方法分项累加(尺寸价 + 饼底价 + 配料数 × 单价),返回格式化后的两位小数金额;
- 安全日志写入:saveToFile() 使用 fopen(..., "a+") 并添加错误处理,记录完整订单详情与最终价格,含分隔线提升可读性。
完整可运行代码(pizza.php)
<?php
class Pizza {
const FILE = 'pizza.txt';
const SIZES = ['Large', 'X-Large'];
const TOPPINGS = ['Pepperoni', 'Mushroom'];
const CRUSTS = ['Thin', 'Original', 'DeepDish'];
const PRICE_TOPPINGS = 1.25;
const PRICE_SIZE = [9.95, 12.95];
const PRICE_CRUST = [0, 0, 2.00];
private ?string $customerName = null;
private array $chosenToppings = [];
private int $size = 0;
private int $crust = 0;
public function calculatePizza(): string {
$cost = [
'size' => self::PRICE_SIZE[$this->size],
'crust' => self::PRICE_CRUST[$this->crust],
'toppings' => count($this->chosenToppings) * self::PRICE_TOPPINGS,
];
return number_format(array_sum($cost), 2);
}
public function saveToFile(): self {
$file = fopen(self::FILE, 'a+') or die('Unable to open pizza.txt for writing!');
fwrite($file, "Size:" . self::SIZES[$this->size] . "\n");
foreach ($this->chosenToppings as $idx) {
if (isset(self::TOPPINGS[$idx])) {
fwrite($file, "Topping:" . self::TOPPINGS[$idx] . "\n");
}
}
fwrite($file, "Crust:" . self::CRUSTS[$this->crust] . "\n");
fwrite($file, "Name:" . ($this->customerName ?: 'Anonymous') . "\n");
fwrite($file, "Price:$" . $this->calculatePizza() . "\n");
fwrite($file, str_repeat('-', 40) . "\n");
fclose($file);
return $this;
}
public function getThankYou(bool $echo = true): string {
$output = "<h1>Pizza ordered, thank you {$this->customerName}!</h1>
<h1>Your total is \${$this->calculatePizza()}</h1>";
if ($echo) echo $output;
return $output;
}
// 简化 setter(生产环境建议用更严格的验证)
public function setSize(string|int $size): self {
$idx = is_int($size) ? $size : array_search($size, self::SIZES);
$this->size = $idx !== false ? $idx : 0;
return $this;
}
public function setCrust(string|int $crust): self {
$formatted = str_replace(' ', '', $crust); // 处理 "Deep Dish" → "DeepDish"
$idx = is_int($crust) ? $crust : array_search($formatted, self::CRUSTS);
$this->crust = $idx !== false ? $idx : 0;
return $this;
}
public function addChosenToppings(array $toppings): self {
foreach ($toppings as $topping) {
if (!$topping) continue;
$idx = is_int($topping) ? $topping : array_search($topping, self::TOPPINGS);
if ($idx !== false && !in_array($idx, $this->chosenToppings)) {
$this->chosenToppings[] = $idx;
}
}
return $this;
}
public function setCustomerName(?string $name): self {
$this->customerName = trim($name) ?: 'Anonymous';
return $this;
}
}
// 表单处理入口
if ($_POST['submit'] ?? false) {
$pizza = new Pizza();
$pizza->setSize($_POST['size'] ?? 'Large')
->setCrust($_POST['crust'] ?? 'Thin')
->addChosenToppings([
$_POST['pepperoni'] ?? null,
$_POST['mushrooms'] ?? null,
])
->setCustomerName($_POST['name'] ?? '')
->saveToFile()
->getThankYou();
} else {
include 'pizza.html'; // 显示表单页
}关键注意事项与最佳实践
- ✅ 输入验证不可省略:示例中已对 $_POST 值做存在性检查(??)和数组搜索容错,但生产环境应增加 filter_var() 或专用验证库校验字符串长度、字符集。
- ✅ 文件写入需权限与异常处理:确保 pizza.txt 可写(如 chmod 664 pizza.txt),且 Web 服务器用户有权限;fopen() 后必须检查返回值,避免静默失败。
- ⚠️ 避免价格硬编码在前端:所有价格逻辑严格置于 PHP 后端,防止用户通过浏览器开发者工具篡改 value 属性绕过计价。
- ⚠️ checkbox 多选特性:HTML 中未勾选的 checkbox 不会提交,因此 $_POST['pepperoni'] 可能不存在——必须用 ?? null 或 isset() 判断,而非直接取值。
- ? 安全性增强建议:后续可集成 CSRF Token、对 name 字段执行 htmlspecialchars() 防 XSS、使用 PDO 替代文件日志实现数据库持久化。
该方案不仅精准满足“按选择计算总价”需求,更以清晰的职责划分、防御性编程和可扩展结构,为同类业务逻辑提供了即插即用的工程化范本。



















