
本文详解如何使用php面向对象方式动态计算披萨订单总价,支持尺寸、配料和饼底类型组合计价,并将订单详情与金额安全写入文件,兼顾可维护性与健壮性。
本文详解如何使用php面向对象方式动态计算披萨订单总价,支持尺寸、配料和饼底类型组合计价,并将订单详情与金额安全写入文件,兼顾可维护性与健壮性。
在电商类表单处理中,准确、可扩展地计算商品总价是后端逻辑的核心能力之一。本文以披萨订单系统为例,展示如何用现代PHP(≥8.0)面向对象设计替代原始的流程式脚本,实现清晰、易测试、易维护的价格计算逻辑。
核心设计思路
我们封装一个 Pizza 类,将业务规则(价格映射、选项校验、金额聚合)与数据操作(文件写入、响应输出)解耦。关键优势包括:
- ✅ 配置集中化:尺寸、饼底、配料及其单价统一定义为常量,便于后续调整;
- ✅ 类型安全与容错:自动处理空值、非法选项(如未选饼底),默认回退至合理值;
- ✅ 灵活扩展性:新增配料或尺寸只需修改常量数组,无需重写计算逻辑;
- ✅ 职责分离:calculatePizza() 专注金额计算,saveToFile() 专注持久化,getThankYou() 专注响应渲染。
完整实现代码(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.00, 0.00, 2.00];
private ?string $customerName = null;
private array $chosenToppings = [];
private ?int $size = 0;
private ?int $crust = 0;
/**
* 计算订单总金额(含尺寸、饼底、配料)
*/
public function calculatePizza(): float {
$costs = [
'size' => self::PRICE_SIZE[$this->getSize()],
'crust' => self::PRICE_CRUST[$this->getCrust()],
'toppings' => count($this->getChosenToppings()) * self::PRICE_TOPPINGS,
];
return (float) number_format(array_sum($costs), 2);
}
/**
* 将订单信息追加写入 pizza.txt(含格式化分隔)
*/
public function saveToFile(): self {
$file = fopen(self::FILE, 'a') or die('Unable to open pizza.txt for writing!');
fwrite($file, "Size: " . self::SIZES[$this->getSize()] . "\n");
foreach ($this->getChosenToppings() as $idx) {
fwrite($file, "Topping: " . self::TOPPINGS[$idx] . "\n");
}
fwrite($file, "Crust: " . self::CRUSTS[$this->getCrust()] . "\n");
fwrite($file, "Name: " . ($this->getCustomerName() ?: 'Unknown') . "\n");
fwrite($file, "Price: $" . $this->calculatePizza() . "\n");
fwrite($file, str_repeat('-', 40) . "\n");
fclose($file);
return $this;
}
/**
* 生成并输出感谢页面(含客户名与总价)
*/
public function getThankYou(bool $echo = true): string {
$html = Pizza ordered, thank you {$this->getCustomerName()}!
<h1>Your total is \${$this->calculatePizza()}</h1>
HTML;
if ($echo) echo $html;
return $html;
}
// --- 属性设置器(支持字符串/数字输入,自动标准化)---
public function setSize(string|int|null $size): self {
if (is_string($size) && in_array($size, self::SIZES)) {
$this->size = array_search($size, self::SIZES);
} elseif (is_int($size) && isset(self::SIZES[$size])) {
$this->size = $size;
} else {
$this->size = 0; // 默认 Large
}
return $this;
}
public function setCrust(string|int|null $crust): self {
if (is_string($crust) && in_array($crust, self::CRUSTS)) {
$this->crust = array_search($crust, self::CRUSTS);
} elseif (is_int($crust) && isset(self::CRUSTS[$crust])) {
$this->crust = $crust;
} else {
$this->crust = 0; // 默认 Thin
}
return $this;
}
public function addChosenToppings(array $toppings): self {
foreach ($toppings as $topping) {
if (empty($topping)) continue;
if (is_string($topping) && in_array($topping, self::TOPPINGS)) {
$this->chosenToppings[] = array_search($topping, self::TOPPINGS);
} elseif (is_int($topping) && isset(self::TOPPINGS[$topping])) {
$this->chosenToppings[] = $topping;
}
}
return $this;
}
public function setCustomerName(?string $name): self {
$this->customerName = trim($name) ?: 'Unknown';
return $this;
}
// --- 获取器 ---
public function getCustomerName(): ?string { return $this->customerName; }
public function getChosenToppings(): array { return $this->chosenToppings; }
public function getSize(): int { return $this->size ?? 0; }
public function getCrust(): int { return $this->crust ?? 0; }
}
// === 请求处理入口 ===
if ($_POST['submit'] ?? false) {
$pizza = new Pizza();
$pizza
->setSize($_POST['size'] ?? null)
->setCrust($_POST['crust'] ?? null)
->addChosenToppings([
$_POST['pepperoni'] ?? null,
$_POST['mushrooms'] ?? null,
])
->setCustomerName($_POST['name'] ?? null)
->saveToFile()
->getThankYou();
} else {
include 'pizza.html'; // 未提交时显示表单
}
关键注意事项与最佳实践
- 安全性提醒:本例未包含XSS防护与SQL注入防御(因无数据库),实际生产环境需对 $_POST 数据进行 htmlspecialchars() 转义后再输出;
- 文件写入风险:fopen(..., 'a') 在高并发下可能产生竞态条件,建议改用数据库或添加文件锁(flock());
- 表单兼容性:HTML中
- 错误处理增强:可在 saveToFile() 中捕获 fwrite() 返回值,判断是否写入成功并记录日志;
- 价格精度:使用 number_format(..., 2) 确保金额始终保留两位小数,避免浮点误差影响用户体验。
通过此实现,你不仅获得了一个功能完备的披萨计价系统,更掌握了一套可复用于其他商品订单场景的PHP OOP建模方法——将业务规则显式化、状态管理规范化、副作用隔离化。下一步,可轻松扩展为支持优惠券、会员折扣或多商品购物车。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











