📘 PHP 访问控制完全指南
本文涵盖 PHP 7.1 ~ PHP 8.4 的访问控制特性,包括类属性、常量、继承、对象间访问等核心概念,适合初学者入门和高级开发者查漏补缺。
一、基础访问控制关键字
PHP 提供三种访问修饰符,用于控制类成员(属性、方法、常量)的可见性:
修饰符 | 含义 | 谁可以访问 |
---|---|---|
public |
公共的 | 任何地方(类内、类外、子类) |
protected |
受保护的 | 类内、子类中,不能在类外部直接访问 |
private |
私有的 | 仅在定义它的类内部 可访问,子类和外部都不能直接访问 |
✅ 示例:
phpclass ParentClass { public $pub = "public"; protected $pro = "protected"; private $pri = "private"; } class ChildClass extends ParentClass { public function test() { echo $this->pub; // ✅ 可以 echo $this->pro; // ✅ 可以 // echo $this->pri; // ❌ 报错!private 不能继承 } }
二、PHP 7.1+:常量的访问控制
从 PHP 7.1.0 开始,类常量也可以设置访问级别:
php
class Math {
public const PI = 3.14;
protected const E = 2.718;
private const GOLDEN_RATIO = 1.618;
}
- 如果不写修饰符,默认是
public
。 private const
只能在类内部使用,子类无法访问。
三、PHP 8.0+:构造函数参数提升(Property Promotion)
从 PHP 8.0 开始,可以在构造函数中直接声明并初始化属性:
php
class User {
public function __construct(
public string $name,
private int $id,
readonly protected string $role
) {}
}
这等价于手动声明属性 + 构造函数赋值。
⚠️ 注意:
readonly
是 PHP 8.2 引入的。
四、PHP 8.4 新特性:属性的不对称可见性
🌟 核心概念
从 PHP 8.4 开始,属性的"读"(get
)和"写"(set
)可以有不同的访问权限!
✅ 语法规则
-
set
的可见性不能比get
更宽- ✅
public get; protected set;
- ❌
protected get; public set;
(写比读还开放?不行!)
- ✅
-
两种写法等价
写法 | 示例 |
---|---|
紧凑语法(推荐用于构造函数) | public private(set) string $title |
块语法(更清晰灵活) | public string $title { get; private set; } |
✅ 两者功能完全一样,只是风格不同。
✅ 常见用法
php
class Book {
// 任何人都能读,但只能在类内部写
public private(set) string $title;
// 任何人都能读,子类也能写
public protected(set) string $author;
// 只有类内部能读写
private private(set) int $id;
}
五、继承中的属性重写规则
子类可以重写父类的属性,但有严格限制:
✅ 允许重写的条件
- 父类属性 不是
final
- 父类属性 不是
private(set)
- 子类的
get
可见性 ≥ 父类 - 子类的
set
可见性 ≥ 父类
✅ 示例
php
class Book {
protected string $title; // get: protected, set: protected
public protected(set) string $author; // get: public, set: protected
}
class SpecialBook extends Book {
public protected(set) $title; // ✅ OK: get 更宽 (protected → public)
public string $author; // ✅ OK: set 更宽 (protected → public)
}
❌ 特殊限制:private(set)
属性是 final
的!
php
class Book {
protected private(set) int $pubYear; // ❌ 这个属性自动变成 final!
}
class SpecialBook extends Book {
public protected(set) int $pubYear; // ❌ Fatal Error!
}
🔥 原因:
private(set)
表示"只有本类能修改",这是对封装的严格保护,不允许子类破坏。
六、private
vs private(set)
:本质区别
特性 | private $prop |
public private(set) $prop |
---|---|---|
是否参与继承 | ❌ 不参与 | ✅ 参与(是公共接口的一部分) |
子类同名属性 | ✅ 允许(是新属性) | ❌ 不允许(会报错) |
外部能否读? | ❌ 不能 | ✅ 能(get 是 public) |
外部能否写? | ❌ 不能 | ❌ 不能 |
是否 final ? |
❌ 否 | ✅ 是(自动 final ) |
✅ 总结:
private(set)
是"可读的私有写属性",属于公共 API;而private
是完全私有的实现细节。
七、高级特性:同类实例间可互访私有成员
✅ 核心规则
同一个类的不同对象实例,可以在类内部互相访问对方的
private
和protected
成员。
✅ 为什么?
因为 private
是 类级别的封装,不是实例级别的。类内部"知道所有实现细节"。
✅ 经典案例:银行转账
php
class BankAccount {
private float $balance;
public function transferTo(BankAccount $other, float $amount): void {
if ($this->balance >= $amount) {
$this->balance -= $amount;
$other->balance += $amount; // ✅ 合法!访问另一个实例的 private 属性
}
}
}
✅ 好处
- 实现对象间协作(如比较、转账)
- 不破坏封装(外部仍无法访问)
- 性能更高(无需 getter/setter)
八、常见陷阱与注意事项
陷阱 | 正确做法 |
---|---|
private( set ) 写法错误 |
❌ 错:private( set ) ✅ 对:private(set) (不能有空格) |
误以为 private(set) 可被重写 |
记住:private(set) 自动 final ,不能覆盖 |
混淆 private $prop 和 private(set) $prop |
前者是完全私有,后者是"可读私写"且可继承 |
✅ 总结图谱
php
PHP 访问控制演进
│
├── PHP 7.1: 常量支持 public/protected/private
├── PHP 8.0: 构造函数参数提升 (public $prop)
├── PHP 8.2: readonly 属性
└── PHP 8.4: 不对称可见性 (private(set), protected(set))
└── set 不能比 get 宽
└── private(set) 自动 final
└── 两种写法:compact 与 block