【PHP 访问控制完全指南】

📘 PHP 访问控制完全指南

本文涵盖 PHP 7.1 ~ PHP 8.4 的访问控制特性,包括类属性、常量、继承、对象间访问等核心概念,适合初学者入门和高级开发者查漏补缺。


一、基础访问控制关键字

PHP 提供三种访问修饰符,用于控制类成员(属性、方法、常量)的可见性:

修饰符 含义 谁可以访问
public 公共的 任何地方(类内、类外、子类)
protected 受保护的 类内、子类中,不能在类外部直接访问
private 私有的 仅在定义它的类内部 可访问,子类和外部都不能直接访问

✅ 示例:

php 复制代码
class 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)可以有不同的访问权限!

✅ 语法规则
  1. set 的可见性不能比 get 更宽

    • public get; protected set;
    • protected get; public set;(写比读还开放?不行!)
  2. 两种写法等价

写法 示例
紧凑语法(推荐用于构造函数) 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;
}

五、继承中的属性重写规则

子类可以重写父类的属性,但有严格限制:

✅ 允许重写的条件

  1. 父类属性 不是 final
  2. 父类属性 不是 private(set)
  3. 子类的 get 可见性 ≥ 父类
  4. 子类的 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 是完全私有的实现细节。


七、高级特性:同类实例间可互访私有成员

✅ 核心规则

同一个类的不同对象实例,可以在类内部互相访问对方的 privateprotected 成员。

✅ 为什么?

因为 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 $propprivate(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