Skip to content

PHP 8.2 新特性

PHP 8.2 引入了只读类(Readonly Classes)、DNF 类型(Disjunctive Normal Form Types)、null/false/true 作为独立类型、敏感参数隐藏(SensitiveParameter)以及 trait 中的常量等特性,进一步增强了 PHP 的类型安全性和表达能力。

前置知识

阅读本节前,建议先了解:PHP 8.1 新特性

只读类(Readonly Classes)

基本用法

php
<?php
declare(strict_types=1);

// PHP 8.1 需要每个属性都标记 readonly
class UserOld
{
    public readonly string $name;
    public readonly string $email;
    public readonly int $age;
}

// PHP 8.2+ 只读类:所有属性自动为 readonly
readonly class User
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}

    // 所有属性都是 readonly,不能被修改
}

// 只读类与只读属性可以混用
readonly class Configuration
{
    public string $appVersion;  // 自动 readonly

    public function __construct(
        string $version,
    ) {
        $this->appVersion = $version;
    }
}

只读类限制

  • 只读类的所有属性都必须是 typed(有类型声明)
  • 只读类不能有动态属性
  • 只读类的子类也必须是 readonly

DNF 类型(Disjunctive Normal Form Types)

基本概念

php
<?php
declare(strict_types=1);

// DNF 类型允许联合类型中包含交叉类型
// 语法:A|(B&C) 表示 A 或 (B 和 C)

function processInput((string&HasId)|(int&HasName) $input): string
{
    // $input 要么是 string&HasId,要么是 int&HasName
    if ($input instanceof HasId && is_string($input)) {
        return "String with ID: {$input->getId()}";
    }

    return "Int with Name: {$input->getName()}";
}

// 实际应用场景
function handleEntity(
    (Countable&Traversable) | array $collection,
): int {
    return count($collection);
}

// 更复杂的 DNF 类型
interface Renderable {}
interface Formattable {}

function render(
    (Renderable & Formattable) | string | null $input,
): string {
    return match (true) {
        $input === null => '',
        is_string($input) => $input,
        default => $input->format(),
    };
}

独立类型 null、false、true

null 类型

php
<?php
declare(strict_types=1);

// PHP 8.2 之前:只能通过 nullable (?) 表示
function findUser(int $id): ?User { }

// PHP 8.2+:null 可以作为独立类型(在联合类型中)
function findUser2(int $id): User|null { }

// null 只能用于联合类型(不能单独使用)
function process(null $value): void {}  // ❌ 错误
function process(null|int $value): void {} // ✅ 正确

false 类型

php
<?php
declare(strict_types=1);

// PHP 8.2+ false 可以作为独立类型
// 主要用于函数可能返回 false 的场景

// strpos() 的返回类型声明
function strpos(string $haystack, string $needle, int $offset = 0): int|false
{
    $pos = \strpos($haystack, $needle, $offset);
    return $pos === false ? false : $pos;
}

// file_get_contents 可能返回 false
function readFileContent(string $path): string|false
{
    $content = file_get_contents($path);
    return $content === false ? false : $content;
}

// 数据库查询
function fetchRow(PDOStatement $stmt): array|false
{
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    return $row === false ? false : $row;
}

// 使用
$result = strpos('Hello World', 'World');
if ($result === false) {
    echo "Not found\n";
} else {
    echo "Found at position {$result}\n";
}

true 类型

php
<?php
declare(strict_types=1);

// true 作为独立类型(PHP 8.2+)

function alwaysReturnsTrue(): true
{
    return true;  // 只能返回 true
}

function validateConfig(array $config): true
{
    // 验证配置
    if (!isset($config['key'])) {
        throw new InvalidArgumentException('Missing key');
    }

    return true;  // 验证通过返回 true
}

// true 在联合类型中的使用
function checkFeature(string $feature): bool
{
    return match ($feature) {
        'new_ui' => true,
        'legacy_mode' => false,
        default => throw new InvalidArgumentException("Unknown feature"),
    };
}

敏感参数隐藏(SensitiveParameter)

php
<?php
declare(strict_types=1);

use SensitiveParameter;

// 在堆栈跟踪中隐藏敏感参数值

class AuthService
{
    public function login(
        string $email,
        #[SensitiveParameter]
        string $password,
    ): bool {
        // ...
        return true;
    }

    public function connect(
        string $host,
        int $port,
        #[SensitiveParameter]
        string $apiKey,
    ): void {
        // ...
    }
}

// 当异常发生在 login() 方法中时,
// 堆栈跟踪中的 password 参数值会被显示为 [object] (SensitiveParameter)
// 而非实际的密码值

Trait 中的常量

php
<?php
declare(strict_types=1);

// PHP 8.2 之前:Trait 中不能定义常量
// PHP 8.2+:Trait 可以定义常量

trait HasTimestamps
{
    public const string CREATED_AT = 'created_at';
    public const string UPDATED_AT = 'updated_at';

    public function getCreatedAt(): ?string
    {
        return $this->{self::CREATED_AT};
    }

    public function setCreatedAt(DateTimeInterface $date): void
    {
        $this->{self::CREATED_AT} = $date->format('Y-m-d H:i:s');
    }
}

trait HasSoftDeletes
{
    public const string DELETED_AT = 'deleted_at';

    public function trashed(): bool
    {
        return $this->{self::DELETED_AT} !== null;
    }
}

class User
{
    use HasTimestamps;
    use HasSoftDeletes;

    public function __construct(
        private array $attributes = [],
    ) {}

    public function __get(string $key): mixed
    {
        return $this->attributes[$key] ?? null;
    }
}

// 访问 trait 常量
echo User::CREATED_AT;     // "created_at"
echo User::UPDATED_AT;     // "updated_at"
echo User::DELETED_AT;      // "deleted_at"

其他改进

readonly 属性可以被重新初始化(通过 cloning)

php
<?php
declare(strict_types=1);

readonly class Point
{
    public function __construct(
        public float $x,
        public float $y,
    ) {}

    // __clone 中可以修改 readonly 属性
    public function __clone()
    {
        $this->x = $this->x * 2;
        $this->y = $this->y * 2;
    }
}

$original = new Point(1.0, 2.0);
$clone = clone $original;  // $clone->x = 2.0, $clone->y = 4.0

新的随机数扩展

php
<?php
declare(strict_types=1);

use Random\Randomizer;
use Random\Engine\Xoshiro256StarStar;

// PHP 8.2+ 新的随机数生成器

// 使用 Randomizer
$randomizer = new Randomizer();

// 随机整数
$int = $randomizer->getInt(1, 100);

// 从数组中随机选择
$item = $randomizer->pick(['a', 'b', 'c', 'd']);

// 打乱数组
$shuffled = $randomizer->shuffleArray([1, 2, 3, 4, 5]);

// 随机字节
$bytes = $randomizer->getBytes(16);

// 密码安全字符串
$password = $randomizer->getBytesFromString(
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%',
    16,
);

// 自定义引擎
$engine = new Xoshiro256StarStar(seed: 42);
$randomizer = new Randomizer($engine);

最佳实践

  1. 使用 readonly class:对于值对象(VO)和不可变实体
  2. 使用 DNF 类型:精确表达复杂的类型约束
  3. 使用 false 类型:准确声明可能失败的函数返回类型
  4. 使用 SensitiveParameter:保护密码、API Key 等敏感信息
  5. 使用 trait 常量:在 trait 中定义共享常量

下一节

继续学习:PHP 8.3 新特性

参考链接