《PHP属性详解:从基础到只读的完全指南》

《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; // ✅ 可以读取

⚠️ 核心规则

  1. 必须初始化readonly 属性必须在构造函数(或 __clone)中赋值一次。
  2. 禁止默认值public readonly string $name = "default";语法错误!只读属性不能有默认值。
  3. 仅限类型化属性:必须先声明类型。
  4. 不支持静态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 标记它。

相关推荐
Echo451 分钟前
Linux的命令和Docker的命令记录
后端
周戬寒4 分钟前
linux和docker的相关命令指示符
后端
菜鸟的迷茫6 分钟前
Spring Cloud Gateway 高级玩法:动态路由、请求日志、限流、灰度发布全方案
后端
高松燈6 分钟前
redis从入门到熟练 (四) 缓存读写一致性问题
后端
文哥打酱油9 分钟前
flowable对已经部署的流程进行更新,不产生新版本
java·后端·spring·flowable
lifallen20 分钟前
Disruptor高性能基石:Sequence并发优化解析
java·数据结构·后端·算法
JohnYan31 分钟前
工作笔记 - NATS的Nkey认证
javascript·后端·rabbitmq
guojl1 小时前
MyBatis插件机制
后端
SimonKing1 小时前
营销级二维码生成术:Java如何打造专属标识
java·后端·程序员
_风不会停息1 小时前
JDK21 虚拟线程的实现原理和应用
java·后端