PHP 8.0
重要提示
PHP 8.0 发布于 2020 年 11 月 26 日,安全修复结束时间为:2023 年 11 月 26 日。如果正在使用当前版本,强烈建议升级到当前版本。
它包含了很多新功能与优化项, 包括命名参数、联合类型、注解、构造器属性提升、match 表达式、nullsafe 运算符、JIT,并改进了类型系统、错误处理、语法一致性。
Nullsafe 运算符
现在可以用新的 nullsafe 运算符链式调用,而不需要条件检查 null。 如果链条中的一个元素失败了,整个链条会中止并认定为 Null。
<?php
class User
{
public function profile()
{
return null; // return new Profile;
}
}
class Profile
{
public function employment()
{
return 'web development';
}
}
$user = new User;
// 之前的写法,先判断是否存在,再进行调用
if ($profile = $user->profile()) {
var_dump($profile->employment());
}
// 8.0 允许通过 nullsafe 进行链式调用
var_dump($user->profile()?->employment() ?? 'Not Provider');
Match 表达式
新的 match
类似于 switch
,并具有以下功能:
- Match 是一个表达式,它可以储存到变量中亦可以直接返回。
- Match 分支仅支持单行,它不需要一个
break;
语句。 - Match 使用严格比较。
强类型检查
与 switch
语句不同,比较是检查 ===
而不是弱相等检查 ==
。
$php = 8.0;
return match($php) {
'8.0' => 'No Match 😭',
8.0 => 'Matched 🥰',
}
// Matched 🥰
未匹配错误
如果未找到匹配项,则会抛出 UnhandledMatchError
。如果愿意,可以通过 try/catch 捕获错误。
$fruit = '🍔';
return match($fruit) {
'🍎' => 'Fruit is an apple',
'🍌' => 'Fruit is a banana',
'🍐' => 'Fruit is a pear',
};
// ❌ Fatel error
// Uncaught UnhandledMatchError
匹配默认值
不必从默认模式返回值。相反,可能会在未找到匹配项时引发自定义错误或异常。
$fruit = '🍔';
return match($fruit) {
'🍎' => 'Fruit is an apple',
'🍌' => 'Fruit is a banana',
'🍐' => 'Fruit is a pear',
default => throw new InvalidFruitException,
};
匹配多个
匹配表达式臂可以包含多个用逗号分隔的表达式。相当于逻辑 OR,并且是具有相同右侧的多个匹配的简写。
$food = '🍎';
return match ($food) {
'🍎', '🍌', '🍊' => 'Food is a Fruit',
'🍔' => 'Food is a burger',
'🍣' => 'Food is a sushi',
}
// Food is a Fruit
范围匹配
通过使用 true
作为匹配的表达式,可以使用匹配表达式来处理条件情况。
此外,还有默认值,此模式匹配以前未匹配的任何内容。
$age = 23;
return match (true) {
$age >= 65 => 'Senior',
$age >= 25 => 'Adult',
$age >= 18 => 'Young adult',
default => 'Child',
}
// Young adult
匹配是否为实例
public function __construct(protected ?Category $category = null) { }
match(true) {
$this->category instanceof Category => $query->where('parent_id', $this->category->id),
default => $query->whereIsRoot()
};
匹配布尔值
match(request()->has('sort')) {
true => $query,
false => $query->defaultOrder(),
};
匹配数组
匹配表达式也可以匹配数组。
$meal = ['🍔', '🍟'];
return match($meal) {
['🍔', '🍕'] => 'Burger and pizza',
['🍔', '🥙'] => 'Burger and tacos',
['🍔', '🍟'] => 'Burger and fries',
}
// Burger and fries
构造器属性提升
在 PHP 8.0 中我们可以通过编写更少的代码来定义并初始化类属性。
<?php
class User
{
public function __construct(protected string $name) { }
}
class Plan
{
public function __construct(protected string $name = 'monthly') { }
}
class Signup
{
/**
* @param User $user
* @param Plan $plan
*/
public function __construct(
protected User $user,
protected Plan $plan
) { }
}
$user = new User('jone_doe');
$plan = new Plan();
$signup = new Signup($user, $plan);
var_dump($signup);
在类的构造函数中可以初始化类属性类型,属性的可访问性,以及赋默认值。
允许对象的 ::class
<?php
class Conversation { }
$object = new Conversation;
var_dump($object::class, get_class($object)); // "Conversation" "Conversation"
在语法上类似于类常量访问,更加直观地期望语法
$object::class
也能正常工作并提供与get_class($object)
相同的结果。
命名参数
这个新的 PHP 8 特性允许您根据参数名称传递函数参数,而不是它们的顺序。
- 仅仅指定必填参数,跳过可选参数。
- 参数的顺序无关、自己就是文档(self-documented)
class Invoice
{
public function __construct(
private $description,
private $total,
private $date,
private $paid,
)
{
//
}
}
return new Invoice(
description: 'Customer installation',
total: 10000,
date: new DateTime,
paid: true,
);
命名参数有一个问题是当我们修改了函数命名的时候,则在调用函数的时候的命名参数也需要一同作修改,否则会抛出:
Uncaught Error: Unknown named parameter
的错误
新的内建函数
字符串 str_starts_with()、 str_ends_with()、str_contains() 函数
# str_starts_with
$id = 'inv_asdasdasdasdasdasd';
var_dump(str_starts_with($id, 'inv_')); // true
# str_ends_with
$id = 'asdasdasdasdasdasd_payment';
var_dump(str_ends_with($id, '_payment')); // true
# str_contains
$url = 'https://example.com?foo=bar';
var_dump(str_contains($url, '?')); // true
get_debug_type 函数
get_debug_type
函数返回给定变量的数据类型。 下面是跟 gettype
函数返回的数据类型对比:
<?php
// 字符串
$string = 'I am a string';
var_dump(get_debug_type($string)); // string(6) "string" 、string(6) "string"
// 整型
$int = 1;
var_dump(get_debug_type($int), gettype($int)); // string(3) "int" 、string(7) "integer"
// 浮点型
$float = 0.1;
var_dump(get_debug_type($float), gettype($float)); // string(5) "float" 、string(6) "double"
// 数组
$array = [1,2];
var_dump(get_debug_type($array), gettype($array)); // string(5) "array" 、string(5) "array"
// 对象
class User {}
$object = new User;
var_dump(get_debug_type($object), gettype($object)); // string(4) "User" 、string(6) "object"
Weak Map 类
Weak map 允许存储链接到对象的任意数据,而不会泄漏任何内存。
<?php
class User {}
$map = new WeakMap();
$user = new User();
$map[$user] = [1, 2, 3];
var_dump(count($map)); // int(1)
unset($user);
var_dump(count($map)); // int(0)
联合类型
相较于以前的 PHPDoc 声明类型的组合,现在可以用原生支持的联合类型声明取而代之,并在运行时得到校验。
<?php
class User
{
protected User|null $user; // 可以在定义类属性时定义对应属性类型为联合类型
public function makeFriendsWith(User|null $user) // 联合类型声明,在php8之前不允许这样定义参数类型
{
$this->user = $user;
var_dump('Yay friends');
}
public function getFriends() : ?User
{
return $this->user;
}
}
$joe = new User;
$sam = new User;
$joe->makeFriendsWith(null);
Attributes 注解
注解功能提供了代码中的声明部分都可以添加结构化、机器可读的元数据的能力, 注解的目标可以是类、方法、函数、参数、属性、类常量。
通过 反射 API 可在运行时获取注解所定义的元数据。
因此注解可以成为直接嵌入代码的配置式语言。
<?php
#[Attribute]
class ApplyMiddleware
{
public function __construct(private string $middleware)
{
//
}
public function getMiddleware() : string
{
return $this->middleware;
}
}
#[ApplyMiddleware('class')]
class MyController
{
#[ApplyMiddleware('property')]
protected $myProperty;
#[ApplyMiddleware('method')]
public function index() {}
}
$controller = new MyController;
$reflectionClass = new ReflectionClass($controller);
// 类
$attributes = $reflectionClass->getAttributes(ApplyMiddleware::class);
$middleware = $attributes[ 0 ]?->newInstance()->getMiddleware();
var_dump($middleware); // string(5) "class"
// 方法
$attributes = $reflectionClass->getMethod('index')->getAttributes();
$middleware = $attributes[0] ?->newInstance()->getMiddleware();
var_dump($middleware); // string(6) "method"
// 属性
$attributes = $reflectionClass->getProperty('myProperty')->getAttributes();
$middleware = $attributes[0]?->newInstance()->getMiddleware();
var_dump($middleware); // string(8) "property"
throw 作为表达式
允许在接受表达式的任何上下文中使用 throw
关键字。
<?php
// callable
$callable = fn() => throw new Exception();
// $value 的值仅不为null
$value = $nullableVariable ?? throw new InvalidArgumentException();
// $value 的值仅为true
$value = $falsableValue ?: throw new InvalidArgumentException();
// 跟使用if表达式一样,让代码变得简洁
$condition && throw new Exception();
$condition || throw new Exception();
$condition and throw new Exception();
$condition or throw new Exception();
无变量捕捉
PHP 8.0 及更高版本允许使用 try/catch 块,其中 catch() 语句不会将异常本身捕获到变量中。
在 PHP 8.0 之前,典型的 PHP try/catch 块必须在 catch 语句中捕获异常:
try {
// try something
}
catch (\InvalidArgumentException $ex) { // "$ex" is required
// handle the exception
}
有时,异常类型(如\InvalidArgumentException
)足以决定异常的处理方式,并将异常捕获到一个变量中(如上例中的$ex),PHP 8.0 允许删除异常捕获。
try {
throw new InvalidArgumentException();
} catch (InvalidArgumentException) {
echo "Something went wrong.";
}
确保在程序中捕获的异常类型的粒度足以传达异常的含义。 例如,如果打算记录事件,则在不捕获异常的情况下捕获通配符 \Exception
或 \Throwable
可能不是一个好习惯。
新的混合伪类型
当函数的参数和返回值没有定义时,默认为 mixed
类型的返回值。
mixed
等同于联合类型 string|int|float|bool|null|array|object|callable|resource
function dump(mixed $var) {
var_dump($var);
}
dump('test');
注意:
mixed
不能跟其他类型一起组合成联合类型。例如:function (mixed|FooClass $bar): int|mixed {}
会抛出错误:Fatal error: Type mixed can only be used as a standalone type in ... on line ...
新 Stringable 接口
class User
{
public function __toString() : string
{
return 'The username';
}
public function passString(string|Stringable $string): string|Stringable
{
return $string;
}
}
var_dump(
(string) (new User), // string(12) "The username"
(new User) instanceof Stringable, // bool(true)
(new User)->passString(new User) // object(User)#2 (0) {}
);
尾随逗号
PHP 8.0 语法允许在参数列表和闭包使用列表中留下尾随逗号。
<?php
class User
{
public function __construct(
public string $name,
protected int $age,
private string $email,
public bool $is_admin = false,
) {}
}
$user = new User(
'Curder',
30,
'q.curder@gmail.com',
true,
);
function() use ($foo, $bar,) {}
ValueError 错误类型
PHP 8 引入了一种称为 ValueError
的新型异常。
try {
$random_element = array_rand([1, 2, 3,], 4); // 函数传递错误的参数
var_dump($random_element);
} catch (ValueError) {
// Something went error
}