简介

PHP 8.3 引入了许多新特性,包括类型化类常量(Typed Class Constants)、#[\Override] 属性、json_validate() 函数、只读属性的深度克隆等。本文将介绍主要变化和如何从 PHP 8.2 迁移到 PHP 8.3。

参考资源

PHP 8.3 新特性

类型化类常量(Typed Class Constants)

PHP 8.3 允许为类常量声明类型,增强了类型安全性:

interface DatabaseInterface {
    public const string ENGINE = 'mysql';
}

trait ConfigTrait {
    final protected const int MAX_CONNECTIONS = 100;
}

enum Status: string {
    public const string DEFAULT = 'pending';
    case Pending = 'pending';
    case Active = 'active';
}

class Database implements DatabaseInterface {
    use ConfigTrait;

    // 类型遵循 LSP(里氏替换原则),子类可以收窄类型
    public const string ENGINE = 'postgresql';
}

类常量类型遵循里氏替换原则(LSP),子类可以收窄父类常量的类型:

class ParentClass {
    public const string|int VALUE = 'MyValue';
}

class ChildClass extends ParentClass {
    // ✅ 可以将 string|int 收窄为 string
    public const string VALUE = 'MyValue';
}

#[\Override] 属性

新的 #[\Override] 属性确保方法确实覆盖了父类方法,有助于捕获拼写错误和简化重构:

class ParentClass {
    protected function setUp(): void {}
}

class ChildClass extends ParentClass {
    #[\Override]
    protected function setUp(): void {} // ✅ 正确覆盖

    #[\Override]
    protected function setUpP(): void {} // ❌ Fatal error: 父类没有该方法
}

这在重构时特别有用 - 如果父类方法被重命名或删除,PHP 会立即报错而不是静默失败。

动态类常量和枚举成员访问

PHP 8.3 支持使用变量语法访问类常量,无需使用 constant() 函数:

class Config {
    const DATABASE = 'mysql';
    const CACHE = 'redis';
}

$key = 'DATABASE';

// PHP 8.3 之前
echo constant(Config::class . "::{$key}");

// PHP 8.3 - 新语法
echo Config::{$key}; // 输出: mysql

同样适用于枚举:

enum Color: string {
    case Red = '#FF0000';
    case Green = '#00FF00';
}

$color = 'Red';
echo Color::{$color}->value; // 输出: #FF0000

只读属性的深度克隆

PHP 8.3 允许在 __clone() 方法中重新初始化只读属性,解决了深度克隆的问题:

readonly class User {
    public function __construct(
        public string $name,
        public DateTime $createdAt,
    ) {}

    public function __clone(): void {
        // PHP 8.3 允许在 __clone() 中修改只读属性
        $this->createdAt = clone $this->createdAt;
    }
}

$user1 = new User('Alice', new DateTime());
$user2 = clone $user1;
// $user2->createdAt 现在是独立的副本

json_validate() 函数

新的 json_validate() 函数用于验证 JSON 字符串语法,比 json_decode() 更高效:

// 验证 JSON 语法
var_dump(json_validate('{"name": "PHP", "version": 8.3}')); // true
var_dump(json_validate('{invalid json}')); // false
var_dump(json_validate('[1, 2, 3]')); // true

// 相比 json_decode(),不需要解码完整内容,更节省内存
if (json_validate($jsonString)) {
    $data = json_decode($jsonString);
}

Random 扩展增强

PHP 8.3 为 Random 扩展增加了新方法:

$randomizer = new Random\Randomizer();

// getFloat() - 生成指定范围的随机浮点数
echo $randomizer->getFloat(0, 10); // 例如: 6.7810757668383

// 使用边界类型
echo $randomizer->getFloat(
    0,
    10,
    Random\IntervalBoundary::ClosedOpen  // [0, 10)
);

// nextFloat() - 生成 [0, 1) 范围的随机浮点数
echo $randomizer->nextFloat(); // 例如: 0.21185336351144

// getBytesFromString() - 从指定字符集生成随机字符串
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
echo $randomizer->getBytesFromString($chars, 16); // 例如: a7b3x9k2m5n8p1q4

匿名类可以是只读的

$instance = new readonly class {
    public function __construct(
        public string $name = 'Anonymous',
    ) {}
};

echo $instance->name; // Anonymous

INI 环境变量回退值

PHP 8.3 支持在 INI 文件中为环境变量设置回退值:

; 如果 DB_HOST 环境变量未设置,使用 localhost 作为默认值
database.host = ${DB_HOST:-localhost}

PHP 8.3 新增函数

mb_str_pad() - 多字节字符串填充

// 填充多字节字符串到指定长度
echo mb_str_pad('中国', 6, '爱', STR_PAD_RIGHT) . "\n"; // 中国爱爱爱爱
echo mb_str_pad('中国', 6, '爱', STR_PAD_LEFT) . "\n";  // 爱爱爱爱中国
echo mb_str_pad('中国人', 6, '爱', STR_PAD_BOTH) . "\n"; // 爱中国人爱爱

str_increment() 和 str_decrement()

// 字符串递增
echo str_increment('a');   // 'b'
echo str_increment('z');   // 'aa'
echo str_increment('A9');  // 'B0'
echo str_increment('foo'); // 'fop'

// 字符串递减
echo str_decrement('b');   // 'a'
echo str_decrement('aa');  // 'z'
echo str_decrement('B0');  // 'A9'

stream_context_set_options()

// 设置流上下文选项
$context = stream_context_create();
stream_context_set_options($context, [
    'http' => [
        'method' => 'GET',
        'header' => 'Accept: application/json',
    ],
]);

DateTime 新方法

// 从 DateTime 创建 DateTimeImmutable
$dt = new DateTime('2024-01-01');
$immutable = DateTimeImmutable::createFromMutable($dt);

// IntlCalendar 新方法
$calendar = IntlCalendar::createInstance();
$calendar->setDate(2024, 0, 15);           // 设置日期
$calendar->setDateTime(2024, 0, 15, 10, 30, 0); // 设置日期时间

DOM 扩展新方法

$doc = new DOMDocument();
$doc->loadHTML('<div class="box" id="main">Hello</div>');
$element = $doc->getElementById('main');

// 获取所有属性名
$names = $element->getAttributeNames(); // ['class', 'id']

// 切换属性
$element->toggleAttribute('hidden'); // 添加 hidden 属性
$element->toggleAttribute('hidden'); // 移除 hidden 属性

// 检查包含关系
$parent = $doc->documentElement;
var_dump($parent->contains($element)); // true

// 获取根节点
$root = $element->getRootNode();

// 比较节点
var_dump($element->isEqualNode($element)); // true

命令行 Linter 改进

# PHP 8.3 支持同时检查多个文件
php -l file1.php file2.php file3.php

# 输出:
# No syntax errors detected in file1.php
# No syntax errors detected in file2.php
# No syntax errors detected in file3.php

弃用特性

get_class() 和 get_parent_class() 无参数调用

class MyClass {
    public function getName(): string {
        // ❌ PHP 8.3 已弃用
        return get_class();

        // ✅ 使用 $this 或 self
        return get_class($this);
        // 或
        return self::class;
    }

    public function getParentName(): string {
        // ❌ PHP 8.3 已弃用
        return get_parent_class();

        // ✅ 使用 $this 或 self
        return get_parent_class($this);
        // 或
        return parent::class;
    }
}

Assert 相关设置

// ❌ 以下 INI 设置已弃用
// assert.active
// assert.bail
// assert.callback
// assert.exception
// assert.warning

// ❌ assert_options() 函数已弃用
assert_options(ASSERT_ACTIVE, true);

// ✅ 使用 ini_set() 替代
ini_set('zend.assertions', 1);

MT_RAND_PHP 变体

// ❌ PHP 8.3 已弃用
mt_srand(1234, MT_RAND_PHP);

// ✅ 使用默认的 Mersenne Twister 实现
mt_srand(1234, MT_RAND_MT19937);

兼容性变化

空数组负索引行为变化

$arr = [];
$arr[-5] = 'a';
$arr[] = 'b';

var_dump($arr);
// PHP 8.3:    [-5 => 'a', -4 => 'b']
// PHP < 8.3: [-5 => 'a', 0 => 'b']

range() 函数行为变化

// PHP 8.3 更严格地处理参数类型
range('a', 'z');           // ✅ 正常
range(1, 10);              // ✅ 正常
range(1.5, 5.5);           // ✅ 正常
range('a', 1);             // ⚠️ PHP 8.3 会产生警告

SQLite3 默认错误模式

// PHP 8.3 中 SQLite3 默认使用异常模式
$db = new SQLite3(':memory:');
// 错误现在会抛出异常而不是返回 false

DateTime 异常类型细化

PHP 8.3 为 DateTime 相关操作引入了更具体的异常类型:

try {
    new DateTime('invalid date');
} catch (DateMalformedStringException $e) {
    // PHP 8.3 新增的具体异常类型
    echo $e->getMessage();
}

unserialize() 错误级别提升

// PHP 8.3 将 E_NOTICE 提升为 E_WARNING
// 反序列化时的错误现在产生警告而不是通知
$data = unserialize('invalid');

其他改进

gc_status() 返回更多信息

$status = gc_status();
// PHP 8.3 返回额外的垃圾回收信息
var_dump($status);
// 包含 running, protected, full 等新字段

类别名支持内置类

// PHP 8.3 允许为内置类创建别名
class_alias('stdClass', 'MyStdClass');
$obj = new MyStdClass();

OpenSSL EC 密钥生成

// 使用自定义参数生成 EC 密钥
$config = [
    'curve_name' => 'prime256v1',
    'private_key_type' => OPENSSL_KEYTYPE_EC,
];
$key = openssl_pkey_new($config);

总结

PHP 8.3 带来了许多实用的新特性和改进:

主要新特性:

  • 类型化类常量
  • #[\Override] 属性
  • 动态类常量访问
  • 只读属性的深度克隆
  • json_validate() 函数
  • Random 扩展增强
  • mb_str_pad() 函数

需要注意的变化:

  • 空数组负索引行为变化
  • get_class()get_parent_class() 无参数调用已弃用
  • Assert 相关 INI 设置已弃用
  • SQLite3 默认使用异常模式
  • DateTime 异常类型细化

通过了解这些变化并遵循迁移建议,可以顺利地从 PHP 8.2 迁移到 PHP 8.3。