在PHP开发中,声明只读变量可以有效避免变量被意外修改,提升代码的健壮性和可维护性。PHP提供了多种不同的方式来实现只读变量的效果,不同方式适用于不同的开发场景。

使用const声明类常量
const关键字可以在类中声明常量,类常量一旦定义就不能被修改,属于只读属性。类常量不需要实例化类就可以访问,访问时使用双冒号语法。
<?php
class User {
// 声明类常量,属于只读属性
const ROLE_ADMIN = 1;
const ROLE_NORMAL = 2;
public function getRoleName($role) {
if ($role == self::ROLE_ADMIN) {
return '管理员';
} elseif ($role == self::ROLE_NORMAL) {
return '普通用户';
}
return '未知角色';
}
}
// 访问类常量,不需要实例化对象
echo User::ROLE_ADMIN; // 输出1
// 尝试修改类常量会报错
// User::ROLE_ADMIN = 3; // 这行代码会触发错误
?>
需要注意的是,const声明的常量只能在类、接口或者全局作用域中使用,不能在函数内部或者普通代码块中声明。而且类常量的值必须是固定的,不能是变量、表达式或者函数调用的结果。
使用define声明全局常量
define函数可以声明全局常量,全局常量在整个脚本执行过程中都是只读的,不能被重新赋值。全局常量可以在任何作用域中访问,不需要额外的访问语法。
<?php
// 声明全局常量
define('APP_NAME', '用户管理系统');
define('APP_VERSION', '1.0.0');
// 访问全局常量
echo APP_NAME; // 输出用户管理系统
// 尝试修改全局常量会报错
// define('APP_NAME', '新的系统名称'); // 重复定义常量会触发警告,值不会被修改
// APP_NAME = '测试'; // 直接赋值会触发错误
?>
define声明的常量默认是大小写敏感的,如果需要大小写不敏感的常量,可以在define函数的第三个参数传入true。另外,PHP7之后define也支持声明数组类型的常量。
使用readonly关键字(PHP8.1+)
PHP8.1版本新增了readonly关键字,可以用于声明类的只读属性。只读属性只能在声明时或者构造函数中赋值,之后就不能被修改,不管是类内部还是外部都不可以修改。
<?php
class Product {
// 声明只读属性
public readonly string $name;
public readonly float $price;
public function __construct(string $name, float $price) {
// 构造函数中为只读属性赋值
$this->name = $name;
$this->price = $price;
}
public function getInfo() {
return "商品名称:{$this->name},价格:{$this->price}";
}
}
$product = new Product('笔记本电脑', 4999.99);
echo $product->getInfo(); // 输出商品名称:笔记本电脑,价格:4999.99
// 尝试修改只读属性会报错
// $product->name = '手机'; // 这行代码会触发错误
?>
readonly关键字只能用于类的属性,不能用于普通变量。而且只读属性必须指定类型,不能是mixed类型,也不能有默认值(除非默认值是null且属性类型为nullable)。
通过类的getter方法模拟只读属性
在PHP8.1之前,我们可以通过将属性设置为私有,只提供getter方法不提供setter方法的方式,模拟只读属性的效果。这种方式可以在getter方法中添加额外的逻辑,比如数据校验或者格式化。
<?php
class Order {
private string $orderId;
private string $createTime;
public function __construct() {
$this->orderId = uniqid('order_');
$this->createTime = date('Y-m-d H:i:s');
}
// 只读属性的getter方法
public function getOrderId(): string {
return $this->orderId;
}
public function getCreateTime(): string {
return $this->createTime;
}
}
$order = new Order();
echo $order->getOrderId(); // 输出订单ID
echo $order->getCreateTime(); // 输出创建时间
// 无法直接修改私有属性,模拟只读效果
// $order->orderId = 'new_id'; // 这行代码会触发错误
?>
这种方式的缺点是访问属性时需要使用方法调用,不能直接通过对象->属性的方式访问,不过可以通过__get魔术方法优化访问体验,但需要注意__get魔术方法会影响所有未定义或未公开属性的访问。
不同只读变量方式的对比
下面是几种只读变量声明方式的对比,方便开发者根据需求选择:
| 方式 | 适用场景 | 是否支持动态赋值 | 访问方式 |
|---|---|---|---|
| const类常量 | 类相关的固定不变的值 | 否,值必须是固定值 | 类名::常量名 |
| define全局常量 | 全局范围的固定配置值 | 否,值必须是固定值 | 直接写常量名 |
| readonly属性 | PHP8.1+,类的实例相关的只读属性 | 是,可在构造函数赋值 | 对象->属性名 |
| getter模拟 | PHP8.1之前,需要额外逻辑的只读属性 | 是,可在构造函数赋值 | 对象->getXxx()方法 |
在实际开发中,如果是全局的固定配置,优先选择define或者const全局常量;如果是类的固定属性,选择const类常量;如果是PHP8.1以上版本的类的实例属性只读需求,优先使用readonly关键字;如果是低版本PHP或者需要额外逻辑的只读属性,使用getter方法模拟。