封装(Encapsulation)是面向对象编程的四大基石之一。在PHP里,封装的核心思想可以用一句话概括:把数据(属性)和操作这些数据的方法捆绑在一起,形成一个独立的单元——对象,同时对外隐藏内部的具体实现细节。
这样做的好处很实在:外界只能通过你暴来的公开方法来跟对象打交道,而不能直接伸手去修改内部的数据。这让你可以随时调整类的内部实现,而不影响整个系统的其他部分。PHP 通过三个访问控制关键词——public、private、protected——来实现封装。
三个访问修饰符:封装的语法支柱
理解这三个关键词的权限范围,是掌握封装的前提。它们就像是三道不同级别的门禁:
-
public(公开的):没有任何限制。类内、类外、子类,任何地方都能访问。PHP 中类成员默认就是public。 -
private(私有的):较高级别的隔离。只有定义它的那个类自己可以访问,子类也碰不到。 -
protected(受保护的):介于两者之间。类内部和其子类可以访问,但类的外部不行。
我个人的一个习惯是:写新类时先把所有属性都设为 private,只有当明确需要子类继承访问时才改为 protected。这个“默认小权限”的原则帮我避免了很多意料之外的外部篡改。
Public:毫无保留的开放
当一个类的成员被标记为 public 时,就相当于敞开了大门。下面这个 Book 类,属性和方法全是公开的:
class Book {
public $title;
public $price;
public function __construct(string $title = "PHP Basics", int $price = 380) {
$this->title = $title;
$this->price = $price;
}
public function getPrice() {
echo "Price: $this->price\n";
}
public function getTitle() {
echo "Title: $this->title\n";
}
}
$book1 = new Book();
echo "Title: {$book1->title} | Price: {$book1->price}";
输出:
Title: PHP Basics | Price: 380
这里你既可以通过 $book1->getTitle() 方法获取书名,也能直接 $book1->title 拿到值。全公开在写一些小工具或临时脚本时确实方便,但在正式项目里,这种写法几乎等于放弃了封装带来的所有保护——任何一段外部代码都可以随意修改对象的状态,排查问题时你会很难定位是谁改了数据。
Private:把数据锁在类内部
封装的正统实践,是把数据成员设为 private,然后提供公开的方法来作为读写入口。这样外界就无法绕过你设定的逻辑直接触碰数据:
class Book {
private $title;
private $price;
public function __construct(string $title = "PHP Basics", int $price = 380) {
$this->title = $title;
$this->price = $price;
}
public function getTitle() {
echo "Title: $this->title\n";
}
public function getPrice() {
echo "Price: $this->price\n";
}
}
$book1 = new Book();
$book1->getTitle();
$book1->getPrice();
// 下面这行会导致致命错误
// echo "Title: $book1->title Price: $book1->price";
输出:
Title: PHP Basics
Price: 380
如果取消注释之后一行,你会得到一个 Fatal error,因为 $title 和 $price 是 private 的,外部根本无法直接触及。这种做法的好处在于:未来你可以在 getPrice() 里加上折扣计算、货币格式转换等逻辑,调用方一行代码都不用改。相比全 public 的写法,private 加公开方法的组合赋予了类对自己内部数据的绝对控制权。
Protected:为继承留一道门
protected 的权限粒度正好在 public 和 private 之间——外部代码不能访问,但子类可以。这在你设计一个有继承体系的类族时特别有用。
先定义一个 Publication 类,包含一个 private 的 $cost 和一个 protected 的 $name:
class Publication {
private $cost;
protected $name;
public function __construct(string $title = "Learning PHP", int $amount = 450) {
$this->name = $title;
$this->cost = $amount;
}
public function showCost() {
echo "Cost: $this->cost <br/>";
}
public function showName() {
echo "Name: $this->name <br/>";
}
}
$pub = new Publication();
$pub->showName();
$pub->showCost();
输出:
Name: Learning PHP
Cost: 450
同一个类自己的公开方法访问 private 和 protected 属性都是没问题的。接下来让子类试试:
class CustomPublication extends Publication {
public function getInternalName() {
return $this->name; // 可以访问,因为 $name 是 protected
}
}
$childPub = new CustomPublication();
echo $childPub->getInternalName(); // 正常输出: Learning PHP
但如果把 CustomPublication 改成不继承 Publication 的独立类,然后尝试从外部访问 protected 成员,就会触发错误:
class InfoFetcher {
public function showLabel($item) {
echo "Label: $item->label <br/>";
}
}
$fetcher = new InfoFetcher();
$pubObj = new Publication();
$fetcher->showLabel($pubObj); // 报错:不能访问 protected 属性
再看一个完整的错误示例:
class CustomBook {
public function displayTitle($pub) {
echo "Book Name: $pub->name <br/>";
}
}
$bookObj = new CustomBook();
$pubObj = new Publication();
$bookObj->displayTitle($pubObj);
输出:
Fatal error: Uncaught Error: Cannot access protected property Publication::$name
这个错误清晰地说明了 protected 的边界:CustomBook 不是 Publication 的子类,它站在外部代码的位置,无权触碰 $name。这也正是为什么不直接用 public 的原因——protected 给了子类通道,却没有对外敞开,在灵活性和安全性之间取了一个平衡点。
封装的价值不止于“隐藏”
很多人会把封装单纯理解为“把属性藏起来”,但它的真正价值在于 “控制访问通道” 。一旦你通过公开方法统一了数据的进出入口,你就可以在这些方法里添加:
-
数据校验:确保传入的值符合业务规则
-
格式转换:内部用一种格式存,对外输出另一种格式
-
日志记录:追踪数据变更
-
延迟加载:方法被调用时才初始化数据
这些都是直接暴露 public 属性绝对做不到的。
在开发中,我发现封装带来的另一个隐性好处是团队协作效率的提升。当每个人都清楚某个类的公开方法是数据操作的唯一合法入口时,代码审查和问题排查的范围就大幅缩小了——你不需要在代码库的每个角落去寻找可能的属性修改点。
本节课程知识要点
-
封装是把数据和操作数据的方法捆绑成对象,对外隐藏内部实现。
-
public成员在任何地方都能访问,PHP 类成员默认即为public。 -
private成员仅有定义它的类自身可以访问,子类也无法触及。 -
protected成员在类内部和子类中可访问,外部代码不可访问,是继承场景下的折中方案。 -
养成“属性设
private、通过公开方法开放访问”的习惯,有助于构建可维护、可扩展的系统。