在PHP的面向对象编程里,public、private、protected 这三个关键词统称为访问修饰符(Access Modifiers)。它们像是类成员的三道门禁卡,精确控制着属性和方法在代码各处能否被访问。每当你声明一个成员变量或成员方法时,前面都会加上这三个词之一,以此定义它的可见范围。
访问修饰符是实现封装(Encapsulation)的语法基础。封装的核心思路是:把对象的内部数据对外界隐藏起来,只允许通过类提供的公开方法进行受控访问。这样一来,外部代码无法直接修改对象状态,数据的安全性和完整性就有了保障。
三个修饰符的权限一览
用通俗的话来理解它们各自的管辖范围:
-
public:开放,类内部、子类、外部代码都可以访问。 -
protected:家族内部开放,类自身和其子类可以访问,外部代码禁止。 -
private:封闭,只有定义它的那个类自己可以访问,子类都不行。
下面这张对照表让你快速看清它们的区别:
| 特性 / 修饰符 | public | protected | private |
|---|---|---|---|
| 可见范围 | 任何地方 | 类自身及子类 | 仅限定义它的类内部 |
| 同类内访问 | 是 | 是 | 是 |
| 子类中访问 | 是 | 是 | 否 |
| 外部代码访问 | 是 | 否 | 否 |
| 典型用途 | 对外公开的 API 方法 | 继承链用的辅助方法 | 内部实现细节、敏感数据 |
| 继承后 | 继承且可访问 | 继承且可访问 | 继承但不可访问 |
| 封装强度 | 低 | 中 | 高 |
Public:对外敞开的接口
PHP 中,类成员默认就是 public。标记为 public 的属性和方法可以在任何地方被调用——类内部、子类、甚至不相关的代码段。看下面这个简单例子:
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->title 拿到属性值,也可以通过 $book1->getTitle() 方法获取。全公开确实方便,但这也意味着任何代码都可以随意读写对象的状态。我个人在写一些原型或一次性脚本时偶尔会这么干,但在正式项目里,把属性设为 public 等于放弃了封装带来的保护——将来如果要加数据校验或格式转换,你就得在整个代码库里搜寻所有直接访问属性的地方。
Private:强的数据保护
private 把访问权限收束到极窄的范围:只有定义这个成员的类自己能访问,子类和外部代码一概触碰不到。这才是封装理念的标准实现——属性设为 private,通过 public 方法暴露读写入口:
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
如果试图直接 $book1->title,PHP 会抛出一个致命错误,因为 $title 是 private 的,外部代码看不见它。这种模式的价值在于:你可以在 getPrice() 里随时加入折扣逻辑、格式化、日志记录,调用方的代码一行都不用改。相比直接暴露裸属性,private 加公开方法的方式赋予了类对自身数据百分之百的控制权。
Protected:为继承预留的通道
protected 的权限恰好卡在 public 和 private 之间:外部代码不能访问,但子类可以。当你在设计一个需要被子类扩展的类族时,protected 就显得特别有用。
把之前 Book 类的 $title 改成 protected,$price 保持 private:
class Book {
private $price;
protected $title;
function __construct(string $param1 = "PHP Basics", int $param2 = 380) {
$this->title = $param1;
$this->price = $param2;
}
public function getPrice() {
echo "Price: $this->price <br/>";
}
public function getTitle() {
echo "Title: $this->title <br/>";
}
}
$b1 = new Book();
$b1->getTitle();
$b1->getPrice();
同一个类的公开方法访问 private 和 protected 属性都正常。现在让一个子类继承 Book:
class mybook extends Book {
// 没有额外定义成员
}
$childBook = new mybook();
$childBook->getTitle(); // 正常,因为 $title 是 protected
由于子类继承了父类的 public 和 protected 成员,mybook 的对象可以顺利调用从父类继承来的 getTitle()。
但关键点来了:如果 mybook 不是 Book 的子类,而是个独立的类,它试图从外部访问 protected 成员就会直接报错:
class mybook {
public function getmytitle($b) {
echo "Title: $b->title <br/>";
}
}
$b1 = new mybook();
$b = new Book();
$b1->getmytitle($b);
输出:
Fatal error: Uncaught Error: Cannot access protected property Book::$title
mybook 没有继承 Book,它站在“外部代码”的位置,所以无权触碰 $title。我当初学到这里时犯过好几次这个错误,总以为只要是自己写的类就能访问别的类的 protected 成员,其实这个修饰符的“受保护”是针对类族外部而言的。
综合示例:银行账户的权限设计
下面用一个银行账户的场景,把三个修饰符放在同一个例子中对比。这个例子很贴近实际业务逻辑,能帮你理清什么时候该用哪个修饰符:
class BankAccount {
public $accountHolder = "John Doe";
protected $accountBalance = 10000;
private $pinCode = 1234;
public function getAccountInfo() {
echo "Account Holder Name is ";
echo $this->accountHolder;
echo "\n";
echo "Account Balance is ";
echo $this->accountBalance;
echo "\n";
echo "Pin Code is ";
echo $this->pinCode;
echo "\n";
}
protected function calculateInterest() {
$interest = $this->accountBalance * 0.05;
echo "Annual Interest is ";
echo $interest;
echo "\n";
}
private function showPrivateNote() {
echo "This is a private internal note\n";
}
public function accessPrivateMethod() {
$this->showPrivateNote();
}
}
class SavingsAccount extends BankAccount {
public function displayAccountSummary() {
echo "Welcome ";
echo $this->accountHolder;
echo "\n";
echo "Your current balance is ";
echo $this->accountBalance;
echo "\n";
echo "Calculating Interest\n";
$this->calculateInterest();
}
public function tryAccessPrivate() {
echo "Trying to access private data\n";
// 下面两行如果取消注释都会报错
// echo $this->pinCode;
// $this->showPrivateNote();
}
}
$customer = new SavingsAccount();
$customer->displayAccountSummary();
echo "\n";
$customer->getAccountInfo();
echo "\n";
$customer->accessPrivateMethod();
echo "\n";
// 下面这些外部访问会报错
// echo $customer->accountBalance;
// echo $customer->pinCode;
输出:
Welcome John Doe
Your current balance is 10000
Calculating Interest
Annual Interest is 500
Account Holder Name is John Doe
Account Balance is 10000
Pin Code is 1234
This is a private internal note
从这个例子可以清晰看出三个层次的权限逻辑:
-
$accountHolder是public:任何地方都能读取,适合公开的信息,比如户主姓名。 -
$accountBalance是protected:外部代码不能直接碰,但SavingsAccount子类可以通过displayAccountSummary()正常使用它来计算利息。适合需要在继承链享但不对外暴露的数据。 -
$pinCode是private:连SavingsAccount都碰不到,只有BankAccount自己的getAccountInfo()方法能访问。适合密码、密钥这类绝不应该被任何子类或外部代码触及的敏感数据。
同样地,calculateInterest() 设为 protected 让子类能调用但外部不行,showPrivateNote() 设为 private 则彻底封闭,只通过 accessPrivateMethod() 这个公开入口间接调用。
什么时候不用 public,而是用 private 或 protected?
这是我在带新人时被问得比较多的问题。直接给出我的判断思路:
-
如果你确定某个成员会在整个应用程序的任何角落被调用,那就
public。但要谨慎,因为一旦公开,后续改动就可能影响远程调用方。 -
如果你只是想让子类共享某些逻辑,但不希望外部代码直接调用,那就
protected。利息计算就是个好例子——银行内部的计算逻辑应该给各类型账户子类用,但不该让随便哪个外部脚本去触发。 -
如果某个成员纯粹是类的内部辅助工具,或者存放的是敏感数据(密码、密钥、内部状态标记),一律
private。这是封装强度的顶端。
一个我个人踩过的坑:早期写代码时图省事,把很多属性和方法都设成 public,结果项目迭代几版之后,根本搞不清哪些外部代码依赖了这些公开成员,重构时寸步难行。后来养成了一个习惯——新写的类,默认所有属性 private,所有方法 private 或 protected,只有当外部真正需要调用时才逐步开放为 public。这个“默认小权限”的思路在PHP封装设计里帮我绕开了大量隐患。
本节课程知识要点
-
PHP 的访问修饰符有三个:
public(公开)、protected(受保护)、private(私有),控制类成员的可见范围。 -
public成员任何地方都能访问,适合对外 API;protected仅限类自身和其子类访问,适合继承链中的共享逻辑;private只有定义它的类能访问,适合内部实现和敏感数据。 -
子类可以继承
private成员,但无法直接访问它们,这是封装隔离性的重要体现。 -
养成“默认小权限”的习惯,优先使用
private或protected,只在必要时开放为public,有助于构建可维护的系统。