Skip to content

PHP 8.4 新特性

PHP 8.4 是一次重大更新,引入了属性钩子(Property Hooks)、不对称可见性(Asymmetric Visibility)、DOM 命名空间、new MyClass()->method() 链式调用、数组化函数等重量级特性,大幅简化了样板代码并提升了语言表达力。

前置知识

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

属性钩子(Property Hooks)

基本概念

属性钩子允许在不使用 getter/setter 方法的情况下,对属性的读取和写入进行拦截和自定义。这是 PHP 8.4 最核心的新特性。

php
<?php
declare(strict_types=1);

class User
{
    // 使用属性钩子替代 getter/setter
    public string $name {
        get => strtoupper($this->name);
        set => $value;
    }

    // 仅读钩子(只有 get)
    public string $fullName {
        get => "{$this->firstName} {$this->lastName}";
    }

    // 带验证的 set 钩子
    public int $age {
        set(int $value) {
            if ($value < 0 || $value > 150) {
                throw new ValueError("Age must be between 0 and 150");
            }
            $this->age = $value;
        }
    }

    public function __construct(
        private string $firstName,
        private string $lastName,
    ) {}
}

$user = new User('John', 'Doe');
echo $user->fullName;  // "John Doe"
$user->age = 25;       // 正常
$user->age = 200;      // 抛出 ValueError

虚拟属性(Computed Properties)

php
<?php
declare(strict_types=1);

class Rectangle
{
    public float $width;
    public float $height;

    // 计算属性(没有实际的存储)
    public readonly float $area {
        get => $this->width * $this->height;
    }

    public readonly float $perimeter {
        get => 2 * ($this->width + $this->height);
    }

    public readonly bool $isSquare {
        get => $this->width === $this->height;
    }

    public function __construct(float $width, float $height)
    {
        $this->width = $width;
        $this->height = $height;
    }
}

$rect = new Rectangle(5, 3);
echo $rect->area;       // 15
echo $rect->perimeter;  // 16
echo $rect->isSquare;    // false

钩子中的 this 和 value

php
<?php
declare(strict_types=1);

class Product
{
    // $this->name 访问底层存储的值
    // $value 是 set 钩子接收的新值
    public string $name {
        set(string $value) {
            $value = trim($value);
            if (strlen($value) < 2) {
                throw new ValueError("Name must be at least 2 characters");
            }
            $this->name = ucfirst($value);
        }
    }

    // get 钩子中可以使用 $this->name
    public string $displayName {
        get => "[PRODUCT] {$this->name}";
    }
}

不对称可见性

php
<?php
declare(strict_types=1);

// PHP 8.4+ 允许属性的读和写有不同的可见性

class User
{
    // public 可读,private 可写
    public private(set) string $name;

    // public 可读,protected 可写
    public protected(set) int $loginCount = 0;

    // public 可读,private 可写
    public private(set) DateTimeImmutable $createdAt;

    public function __construct(string $name)
    {
        $this->name = $name;
        $this->createdAt = new DateTimeImmutable();
    }

    // 受保护的方法可以修改 loginCount
    public function incrementLoginCount(): void
    {
        $this->loginCount++;
    }
}

$user = new User('John');
echo $user->name;       // ✅ public,外部可读
// $user->name = 'Jane'; // ❌ private(set),外部不可写
$user->incrementLoginCount();  // ✅ 内部可写

// 子类中
class AdminUser extends User
{
    public function resetLoginCount(): void
    {
        $this->loginCount = 0;  // ✅ protected(set),子类可写
    }

    // ❌ private(set) 子类也不能写
    // public function rename(string $name): void {
    //     $this->name = $name;  // Error
    // }
}

DOM 命名空间

php
<?php
declare(strict_types=1);

// PHP 8.4+ DOM API 有了命名空间化的版本
// 新的 DOM 命名空间:DOM\*
// 旧的 DOM\* 类被移到命名空间下

use DOM\Document;
use DOM\Element\HTMLElement;
use DOM\NodeList;

// 创建文档
$doc = new Document();
$doc->loadHTML('<html><body><h1>Hello World</h1></body></html>');

// 使用新的 DOM API(支持现代 DOM 标准)
$h1 = $doc->querySelector('h1');
echo $h1->textContent;  // "Hello World"

// 遍历元素
$elements = $doc->querySelectorAll('p');
foreach ($elements as $element) {
    echo $element->textContent . "\n";
}

链式调用 new MyClass()->method()

php
<?php
declare(strict_types=1);

// PHP 8.4+ 允许在 new 表达式后直接调用方法

// PHP 8.3 及之前
$result = (new UserRepository())->findById(1);
$config = (new Config())->load();

// PHP 8.4+
$result = new UserRepository()->findById(1);
$config = new Config()->load();

// 链式调用
$users = new UserCollection()
    ->filter(fn ($u) => $u->isActive())
    ->map(fn ($u) => $u->getName())
    ->toArray();

// 嵌套 new
$service = new Service(
    new Repository(
        new DatabaseConnection('localhost'),
    ),
);

数组化函数

php
<?php
declare(strict_types=1);

// PHP 8.4+ array_find, array_find_key, array_any, array_all

// array_find - 返回第一个满足条件的元素
$users = [
    ['id' => 1, 'name' => 'John'],
    ['id' => 2, 'name' => 'Jane'],
    ['id' => 3, 'name' => 'Bob'],
];

$user = array_find($users, fn ($u) => $u['name'] === 'Jane');
// ['id' => 2, 'name' => 'Jane']

$user = array_find($users, fn ($u) => $u['name'] === 'Unknown');
// null(未找到)

// array_find_key - 返回第一个满足条件的元素的键
$key = array_find_key($users, fn ($u) => $u['name'] === 'Jane');
// 1

// array_any - 检查是否有元素满足条件
$hasActive = array_any($users, fn ($u) => $u['id'] > 1);
// true

// array_all - 检查是否所有元素都满足条件
$allPositive = array_all([1, 2, 3, 4], fn ($n) => $n > 0);
// true

// PHP 7.x 替代方案
// array_find: array_values(array_filter($arr, $fn))[0] ?? null
// array_any: count(array_filter($arr, $fn)) > 0
// array_all: count(array_filter($arr, $fn)) === count($arr)

其他改进

闭包创建新实例

php
<?php
declare(strict_types=1);

// PHP 8.4+ 闭包支持创建新的类实例
// 使用 $this->new static() 或 $this->new self()

class Builder
{
    public function create(): static
    {
        return new static();
    }
}

新的 parse_url() 组件

php
<?php
declare(strict_types=1);

// PHP 8.4+ 支持 URL 组件常量
$url = 'https://example.com:8080/path?query=value#fragment';

$host = parse_url($url, PHP_URL_HOST);         // "example.com"
$port = parse_url($url, PHP_URL_PORT);          // 8080
$path = parse_url($url, PHP_URL_PATH);           // "/path"
$query = parse_url($url, PHP_URL_QUERY);         // "query=value"
$fragment = parse_url($url, PHP_URL_FRAGMENT);   // "fragment"

最佳实践

  1. 使用属性钩子:替代传统的 getter/setter 模式,减少样板代码
  2. 使用不对称可见性:实现真正的不可变属性
  3. 使用链式 new:简化临时对象创建的代码
  4. 使用数组化函数:更简洁的数组操作

下一节

继续学习:版本迁移指南

参考链接