
本文讲解如何在面向对象的 php 设计中,优雅地模拟数据库外键关系——让 borrower 类能安全访问 book 实例的属性(如 id、可用性),避免硬编码 id 字段,提升可维护性与数据一致性。
本文讲解如何在面向对象的 php 设计中,优雅地模拟数据库外键关系——让 borrower 类能安全访问 book 实例的属性(如 id、可用性),避免硬编码 id 字段,提升可维护性与数据一致性。
在面向对象建模中,直接用整型 ID 字段(如 $id_BookBorrowed)表示“外键”虽可行,但违背了封装与职责分离原则:它将数据关系降级为原始数值耦合,导致业务逻辑(如借书时需同步更新图书 available 状态)分散、易出错且难以测试。
✅ 正确做法是:用对象引用替代 ID 引用。即 Borrower 类应持有 Book 实例(或 Book 集合),而非仅存其 ID。这样可自然调用 ->getAvailable()、->setAvailable(false) 等方法,实现强类型、可追溯的关联操作。
✅ 重构建议(含示例代码)
1. 优化 Book 类:支持多借阅场景(可选)
若系统支持同一本书被多人同时借阅(如图书馆副本制),建议扩展 borrowedBy 关联:
class Book {
public $id_Book, $price, $title, $available;
private $borrowedBy = []; // 存储 Borrower ID 或 Borrower 对象数组
function __construct($id_Book, $price, $title, $available = true) {
$this->id_Book = $id_Book;
$this->price = $price;
$this->title = $title;
$this->available = $available;
}
// 新增:登记借阅者(支持多借阅)
public function borrowBy(Borrower $borrower) {
if ($this->available) {
$this->borrowedBy[] = $borrower->getIdBorrower();
$this->available = false; // 或根据策略设为 false/计数减一
}
}
public function returnFrom(Borrower $borrower) {
$key = array_search($borrower->getIdBorrower(), $this->borrowedBy);
if ($key !== false) {
unset($this->borrowedBy[$key]);
$this->available = empty($this->borrowedBy); // 最后一人归还才设为可用
}
}
// 其余 getter/setter 保持不变...
}2. 重构 Borrower 类:用 Book 对象替代 ID
删除冗余字段 $id_BookBorrowed 和 $hasBorrowed,改用 Book 实例数组管理借阅状态:
立即学习“PHP免费学习笔记(深入)”;
class Borrower {
public $id_Borrower, $name, $password;
private $borrowedBooks = []; // Book[] 数组,支持多本借阅
function __construct($id_Borrower, $name, $password) {
$this->id_Borrower = $id_Borrower;
$this->name = $name;
$this->password = $password;
}
// 借书:传入 Book 实例,自动处理状态
public function borrowBook(Book $book): bool {
if ($book->getAvailable()) {
$book->borrowBy($this); // 通知图书已被借
$this->borrowedBooks[] = $book;
return true;
}
return false; // 图书不可借
}
// 还书:传入 Book 实例或索引
public function returnBook(Book $book): bool {
$key = array_search($book, $this->borrowedBooks, true);
if ($key !== false) {
$book->returnFrom($this);
unset($this->borrowedBooks[$key]);
return true;
}
return false;
}
// 辅助方法:获取当前借阅的图书 ID 列表
public function getBorrowedBookIds(): array {
return array_map(fn($b) => $b->getIdBook(), $this->borrowedBooks);
}
public function getIdBorrower() { return $this->id_Borrower; }
public function getName() { return $this->name; }
}3. 使用示例
// 初始化数据
$book1 = new Book(1, 29.99, "PHP Design Patterns", true);
$borrower1 = new Borrower(101, "Alice", "pass123");
// 借书(自动更新 book.available 和 borrower 状态)
if ($borrower1->borrowBook($book1)) {
echo "✅ {$borrower1->getName()} 成功借阅《{$book1->getTitle()}》\n";
echo "? 图书当前可用: " . ($book1->getAvailable() ? '是' : '否') . "\n";
}
// 还书
if ($borrower1->returnBook($book1)) {
echo "↩️ 已归还,图书恢复可用: " . ($book1->getAvailable() ? '是' : '否') . "\n";
}⚠️ 注意事项与最佳实践
- 不要在类中混用 ID 和对象:一旦选择对象引用模式,就应彻底移除 $id_BookBorrowed 等 ID 字段,避免逻辑歧义。
- 持久化需额外处理:内存中对象关联不等于数据库外键。实际项目中,需配合 ORM(如 Doctrine)或手动编写 DAO 层,将 Borrower→Book 关系同步到数据库 borrower.id_book_borrowed 字段。
- 循环引用风险:若 Book 中保存 Borrower 对象(而非 ID),需注意 __sleep()/序列化问题;推荐在 Book::$borrowedBy 中仅存 ID,或使用弱引用(PHP 8.0+)。
- 性能考量:高频查询场景下,可缓存 Book 实例或采用懒加载(Lazy Load)模式,避免不必要的数据库查询。
? 总结:外键的本质是语义化的关联关系,而非数值映射。用对象属性承载关联,不仅使代码更贴近业务现实,也大幅降低状态不一致风险——这才是 OOP 处理关系型数据的正确姿势。



















