Kodokon kodokon.com

现代 PHP:严格类型、枚举、match

拥抱当今的 PHP:严格类型、箭头函数、nullsafe 运算符、枚举,以及 match 表达式。

9 分钟 · 3 题

在 Kodokon 中打开本课

现代 PHP 拥抱类型。declare(strict_types=1) 放在文件的第一条语句处,会禁用隐式转换:把字符串 '10' 传给一个 float 参数会抛出一个 TypeError,而不是被悄悄地转换。请为参数和返回值标注类型:这样你的错误会及早地、在正确的位置浮现出来,而不是拖到三个文件之后。

PHP
<?php

declare(strict_types=1);

function applyDiscount(
    float $price,
    float $rate = 0.1
): float {
    return round($price * (1 - $rate), 2);
}

echo applyDiscount(49.99);
echo PHP_EOL;
echo applyDiscount(100.0, 0.25);
一个带类型的签名:函数的契约变得明确。

箭头函数 fn 把只有单个表达式的匿名函数浓缩了起来。相比传统闭包,它有一个重大优势:它会自动捕获父作用域的变量,无需 use 子句。它们在配合 array_map()array_filter() 时格外出彩。

PHP
<?php

declare(strict_types=1);

$prices = [12.5, 49.9, 8.0, 120.0];
$rate = 0.2;

$discounted = array_map(
    fn (float $p): float => round($p * (1 - $rate), 2),
    $prices
);

$affordable = array_filter(
    $discounted,
    fn (float $p): bool => $p < 50
);

print_r($affordable);
rate 变量无需 use 子句就被捕获了。

nullsafe 运算符 ?-> 会在链条中的某一环为 null 时立刻短路整个调用,而不是引发一个致命错误。与 ?? 结合使用,它能用一行清晰易读的代码取代层层嵌套的 if 语句金字塔。

PHP
<?php

declare(strict_types=1);

class Address
{
    public function __construct(
        public readonly string $city
    ) {
    }
}

class User
{
    public function __construct(
        public readonly ?Address $address = null
    ) {
    }
}

$alice = new User(new Address('Lyon'));
$bob = new User();

echo $alice->address?->city ?? 'Unknown city';
echo PHP_EOL;
echo $bob->address?->city ?? 'Unknown city';
对 Bob 没有报错:链条在第一个 null 处就停下了。

枚举为封闭的取值集合(各种状态、角色)赋予了真正的类型:创建一个不存在的状态变得不可能。而 match 相比 switch 是一次明显的改进:严格比较、把结果作为一个值返回,以及在没有任何分支匹配时抛出 UnhandledMatchError - 这是一道防止疏漏的保险。

PHP
<?php

declare(strict_types=1);

enum OrderStatus: string
{
    case Pending = 'pending';
    case Shipped = 'shipped';
    case Delivered = 'delivered';

    public function label(): string
    {
        return match ($this) {
            self::Pending => 'Pending',
            self::Shipped => 'Shipped',
            self::Delivered => 'Delivered',
        };
    }
}

$status = OrderStatus::from('shipped');
echo $status->label();
一个以字符串为底层值的枚举:非常适合数据库。

知识检测

确认你已牢记本课的重点内容。

  1. 在 declare(strict_types=1) 之下,如果你把字符串 '10' 传给一个 float 类型的参数,会发生什么?
    • PHP 会悄悄地把字符串转换成 10.0
    • PHP 只发出一个警告并继续运行
    • PHP 会抛出一个 TypeError
  2. 箭头函数 fn() 与传统闭包 function() 有什么区别?
    • 它可以包含好几条语句
    • 它会自动捕获父作用域的变量,无需 use 子句
    • 它运行得更快
    • 它不能声明类型
  3. match 表达式相比 switch 有什么优势?
    • 它进行严格比较(===),返回一个值,并在没有分支匹配时抛出错误
    • 它接受宽松比较(==)以获得更大的灵活性
    • 它允许从一个分支贯穿到下一个分支