《PHP属性详解:从基础到只读的完全指南》
属性基础:类的"部件"
属性是类的变量,代表对象的状态或特征。
ruby
class Robot {
public $name; // 一个简单的属性
}
类型化属性 (PHP 7.4+):给部件"定类型"
避免属性乱装东西。从 PHP 7.4 起,可以明确属性的类型。
php
class Robot {
public string $name; // 必须是字符串
public int $age; // 必须是整数
public ?float $height; // 可以是浮点数或 null
public array $tools; // 必须是数组
// 注意:callable 不能作为属性类型
}
✅ 好处:代码更健壮,IDE 更智能。
只读属性 readonly
(PHP 8.1+):锁定部件
让属性一旦初始化就不可更改。这是实现"不可变对象"的关键。
php
class Robot {
public readonly string $serialNumber; // 序列号,出厂后不能改!
public function __construct(string $serial) {
$this->serialNumber = $serial; // ✅ 构造函数内初始化(唯一机会)
}
}
$robot = new Robot("SN123");
// $robot->serialNumber = "SN456"; // ❌ Fatal error! 不能修改
echo $robot->serialNumber; // ✅ 可以读取
⚠️ 核心规则:
- 必须初始化 :
readonly
属性必须在构造函数(或__clone
)中赋值一次。- 禁止默认值 :
public readonly string $name = "default";
❌ 语法错误!只读属性不能有默认值。- 仅限类型化属性:必须先声明类型。
- 不支持静态 :
static readonly
❌ 不支持。
readonly
的精髓:引用 vs 内容
readonly
保护的是"引用 "(指向哪里),不是"内容"(里面有什么)。
php
class Robot {
public readonly array $inventory; // 只读数组
public readonly object $sensor; // 只读对象
}
$robot = new Robot();
$robot->inventory = ['screwdriver']; // ✅ 初始化
$robot->sensor = new stdClass(); // ✅ 初始化
// ❌ 错误!试图改变"引用"
// $robot->inventory = ['wrench']; // 不行!不能换整个数组!
// $robot->sensor = new stdClass(); // 不行!不能换整个对象!
// ✅ 正确!修改"内容"(内部可变性)
$robot->inventory[] = 'wrench'; // 给 inventory 数组添加新工具 ✅
$robot->sensor->temperature = 25; // 给 sensor 对象添加属性 ✅
总结 :readonly
保证"背包"和"传感器"不被替换,但允许往背包里装东西或给传感器升级。
类级别的 readonly
(PHP 8.2+):一键锁定
直接标记整个类为 readonly
。
php
readonly class Robot {
public function __construct(
public string $name,
public int $age
) {}
}
$robot = new Robot("小助手", 5);
// $robot->name = "大助手"; // ❌ 错误!所有属性只读
// $robot->newProp = "test"; // ❌ 错误!禁止动态属性!
- 效果 :所有声明的属性 自动
readonly
,并禁止创建动态属性。 - 继承 :子类也必须是
readonly
类。
readonly
的"写权限"大变革 (PHP 8.4+)
PHP 8.3 及之前 :readonly
属性的"写权限"是隐式 private
。
- 只有声明该属性的类本身可以在其构造函数中初始化它。
- 子类无法直接初始化继承的
readonly
属性,必须依赖父类构造函数。
scala
// PHP 8.3
class ParentRobot {
public readonly string $name;
}
class ChildRobot extends ParentRobot {
public function __construct(string $name) {
// parent::__construct($name); // ❌ 必须调用父类
// $this->name = $name; // ❌ 子类不能写!
}
}
PHP 8.4+ :readonly
属性的"写权限"变为隐式 protected(set)
!
- "读权限" (
get
) 由public
/protected
/private
决定。 - "写权限" (
set
) 默认为protected
,子类也可以直接初始化!
php
// PHP 8.4+
class ParentRobot {
public readonly string $name;
// 父类甚至可以没有构造函数!
}
class ChildRobot extends ParentRobot {
public function __construct(string $name) {
$this->name = $name; // ✅ 太棒了!子类可以直接初始化!
}
}
- 显式控制 (PHP 8.4+) :可以用
public(set)
,protected(set)
,private(set)
精确控制写权限。
克隆时的 readonly
(PHP 8.3+)
克隆对象时,readonly
属性也需要在新对象中初始化。使用 __clone()
方法。
php
class Robot {
public readonly ?string $status;
public function __clone() {
$this->status = "Cloned"; // ✅ 在克隆时重新初始化
}
}
$robot1 = new Robot();
$robot1->status = "Active";
$robot2 = clone $robot1;
var_dump($robot2->status); // string(6) "Cloned"
final
常量 (PHP 8.1.0+):锁定常量
在 PHP 8.1 之前,只有方法可以被标记为 final
,常量不行。从 PHP 8.1.0 起,常量也可以被标记为 final
。
- 目的:防止子类重新定义(覆盖)某个核心常量,确保其值在继承链中保持不变。
- 效果 :一旦常量被标记为
final
,任何尝试在子类中重新定义它的行为都会导致致命错误。
例子:
php
class MathConstants {
// 普通常量,子类可以覆盖
public const PI = 3.14159;
// final 常量,子类禁止覆盖!
public final const E = 2.71828;
}
// 错误示例:尝试覆盖 final 常量
// class WrongPhysicsConstants extends MathConstants {
// public const E = 2.7; // Fatal error: Cannot override final constant MathConstants::E
// }
// 正确示例:只覆盖非 final 常量
class PhysicsConstants extends MathConstants {
public const PI = 3.14; // ✅ 合法!覆盖非 final 的 PI
// E 常量会直接继承,值为 2.71828
}
// 测试
echo PhysicsConstants::PI; // 输出: 3.14
echo PhysicsConstants::E; // 输出: 2.71828
关键点总结: 如果某个常量的值是"基石"或"标准",不希望被子类修改,就用 final
标记它。