【PHP】新版本特性记录(持续更新)
文章目录
- 前言
- PHP 7.0
- 1)NULL合并运算符:??
- 2)参数、返回值支持类型声明
- 3)太空船操作符:<=>
- 4)通过 define 定义常量数组
- 5)匿名类实例化
- 6)字符串里使用\u转义unicode codepoint
- PHP 7.1
- 1)数组解构赋值
- 2)可为空(Nullable)类型
- 3)新增void返回值类型
- 4)类常量可见性
- 5)多异常捕获处理
- PHP 7.2
- 1)新增object类型
- 2)Sodium成为核心扩展
- PHP 7.3
- 1)数组解构支持引用赋值
- PHP 7.4
- 1)箭头函数
- 2)NULL合并赋值运算符:??=
- 3)类属性支持类型声明
- 4)在数组中使用Spread运算符
- PHP 8.0
- 1)match表达式
- 2)Nullsafe运算符
- 3)命名参数
- 4)联合类型声明
- 5)新增mixed类型
- 6)对象可以通过::class获取类名
- 7)构造器属性提升
- 8)注解
- PHP 8.1
- 1)枚举类型
- 2)Spread运算符支持展开关联数组
- 3)新增never返回值类型
- 4)readonly属性
- 5)交集类型声明
- 6)可以使用final来修饰类常量
- 7)使用new初始化参数
- PHP 8.2
- 1)新增true/false/null类型
- 过时:动态属性
前言
本文用于记录PHP各个版本的新特性,仅记录个人认为比较重要的特性,如果需要全面的信息,请查阅官方文档。
PHP 7.0
官方文档
1)NULL合并运算符:??
NULL合并运算符(Null coalescing operator)是个语法糖,用于简化三元表达式和isset()
的写法。如果变量存在且值不为 NULL
,就会返回自身的值,否则返回第二个操作数。
// php7前
$uid = isset($_GET['uid']) ? $_GET['uid'] : 0;
// 使用NULL合并运算符
$uid = $_GET['uid'] ?? 0;
// 链式使用,如果没有a则获取b,如果没有b则返回默认值0
$uid = $_GET['a'] ?? $_GET['b'] ?? 0;
2)参数、返回值支持类型声明
类型声明可以用于函数的参数、返回值, PHP 7.4.0 起还可以用于类的属性, PHP 8.3.0 起还可以用于类的常量。
function foo(string $str): string
{
return $str . 'hello';
}
如果参数类型和实际传的不一致,在可以转换的前提下,PHP会自动进行一个隐式的转换,如果不可以转换,则会抛出一个 TypeError 错误。例如:
function foo(string $a): void
{
var_dump($a); // 输出:string(3) "123"
}
foo(123); // 传入int,会自动转换为string
foo([1]); // 数组无法转换为string,报错
如果不希望自动转换,可以开启严格模式。在严格模式下,只能接受完全匹配的类型,否则会抛出 TypeError错误,唯一的例外是 int 值也可以传入声明为 float 的类型。
declare(strict_types=1); // 开启严格模式
function foo(string $a): void
{
var_dump($a);
}
foo(123); // 会直接报错
3)太空船操作符:<=>
太空船操作符(Spaceship operator)用于比较两个值的大小,如果相等返回0,如果左值小于右值返回-1,如果左值大于右值返回1
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
4)通过 define 定义常量数组
define('ANIMALS', [
'dog',
'cat',
'bird'
]);
5)匿名类实例化
interface Logger
{
public function save();
}
$app->setLogger(new class implements Logger {
public function save()
{
// TODO: Implement save() method.
}
}
);
6)字符串里使用\u转义unicode codepoint
// 汉字“严”的unicode codepoint是4e25(十六进制)
echo "\u{4e25}" . PHP_EOL; // 输出:严
PHP 7.1
官方文档
1)数组解构赋值
数组解构(Array destructuring)是一种便捷的方式,用于将数组中的元素赋值给变量,这在处理数组或从函数返回多个值时特别有用。在PHP中,可以使用[]
语法进行解构。
关联数组:
$data = [
'name' => 'tim',
'gender' => 'male',
'age' => 18,
];
// 解构赋值
['name' => $name, 'age' => $age] = $data;
var_dump($name, $age); // 输出:tim, 18
索引数组:
$data = ['A', 'B', 'C'];
[$a, $b] = $data; // 获取索引为0和1的元素
[$arr[], $arr[]] = $data; // 将索引0和1的元素放入到$arr数组
[, , $c] = $data; // 如果不提供变量名,则忽略对应位置的元素
[2 => $c] = $data; // 获取索引为2的元素
在循环中使用解构:
$data = [
['id' => 1, 'name' => 'tim'],
['id' => 2, 'name' => 'john'],
];
foreach ($data as ['name' => $name]) {
var_dump($name);
}
2)可为空(Nullable)类型
参数以及返回值的类型现在可以通过在类型前加上一个问号使之允许为null
:
// $str参数要么为字符串,要么为null
function foo(?string $str): ?string
{
}
3)新增void返回值类型
返回值类型为void
的,不能返回任何数据,包括null
。
function foo(): void
{
return; // 合法
return null; // 不合法
}
试图去获取一个 void 方法的返回值会得到 null ,并且不会产生任何警告。
4)类常量可见性
可以为类中的常量设置可见性:
class ConstDemo
{
const PUBLIC_CONST_A = 1; // 默认为public
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}
5)多异常捕获处理
一个catch
语句块现在可以通过管道字符(|
)来实现多个异常的捕获。 这对于需要同时处理来自不同类的不同异常时很有用。
try {
// some code
} catch (FirstException | SecondException $e) {
// handle first and second exceptions
}
PHP 7.2
官方文档
1)新增object类型
function foo(object $obj): object
{
}
2)Sodium成为核心扩展
现代 Sodium 加密类已经成为 PHP 核心扩展。
PHP 7.3
官方文档
1)数组解构支持引用赋值
$arr = [
'tim' => ['uid' => 1],
'john' => ['uid' => 2],
];
['tim' => &$tim] = $arr;
$tim['uid'] = 3; // 此处的修改会影响到$arr
var_dump($arr);
PHP 7.4
官方文档
1)箭头函数
箭头函数是一种更简洁的匿名函数写法,语法是fn(参数) => 表达式
,例如:
$y = 1;
// 普通匿名函数写法
$fn1 = function($x) use ($y) {
return $x + $y;
};
// 箭头函数写法
$fn2 = fn($x) => $x + $y;
var_dump($fn1(2), $fn2(2)); // 打印结果都是3
可见,箭头函数会自动引入外部变量$y
,无需使用use
关键字,更加方便。
但是,如果想要在函数内部修改外部变量的话,只能用匿名函数:
$y = 1;
// 普通匿名函数写法
$fn1 = function() use (&$y) {
$y++;
};
// 箭头函数写法
$fn2 = fn() => $y++;
$fn2();
var_dump($y); // 输出仍然是1,没有变化
$fn1();
var_dump($y); // 输出2
箭头函数的限制:
=>
后面只能跟一个表达式,不能包含多条语句- 不能修改父作用域变量
2)NULL合并赋值运算符:??=
NULL合并赋值运算符(Null coalescing assignment operator)(??=
)是对NULL合并运算符(??
)的一个升级:
$_GET['uid'] ??= 0; // 写法比以下两种写法都要简洁得多
var_dump($_GET['uid']); // 输出:0
// 等同于写法1
$_GET['uid'] = isset($_GET['uid']) ? $_GET['uid'] : 0;
// 等同于写法2
$_GET['uid'] = $_GET['uid'] ?? 0;
3)类属性支持类型声明
类中的属性支持声明类型(callable
除外):
class User
{
public int $uid;
public string $name;
public static int $age;
}
4)在数组中使用Spread运算符
Spread运算符(...
)用于将一个数组展开,现已支持在数组定义中使用:
$sub1 = ['a', 'b'];
$sub2 = ['c', 'd'];
$merge = [...$sub1, ...$sub2]; // 合并两个数组
var_dump($merge);
仅支持展开索引数组,关联数组需在PHP 8.1中才开始支持。
PHP 8.0
官方文档
1)match表达式
match表达式用于对条件的值进行比较,然后进行分支计算,基本用法:
$status = 1;
$ret = match($status) {
0 => '禁用',
1 => '启用',
default => '未知',
};
var_dump($ret); // 输出:启用
match表达式跟switch表达式比较相似,它们之间的区别为:
1)match表达式在比较条件值的时候,用的是严格比较(===
),而switch用的是松散比较(==
):
$status = '1';
$ret = match($status) {
1 => '启用1',
'1' => '启用2',
};
var_dump($ret); // 输出:启用2
2)match支持表达式计算:
$age = 15;
$ret = match(true) {
$age < 18 => '未成年',
$age >= 18 => '已成年',
};
var_dump($ret); // 输出:未成年
甚至调用函数都可以:
$num = 4;
$ret = match($num) {
pow(2, 1) => '2的一次方',
pow(2, 2) => '2的二次方',
};
var_dump($ret); // 输出:2的二次方
逻辑OR:
$num = 4;
$ret = match($num) {
2, 4, 6 => '偶数', // 逻辑OR,当$num为2或4或6时,分支条件成立
};
var_dump($ret); // 输出:偶数
3)match的分支必须列举出所有可能的情况,如果所有分支条件都不成立,会抛出UnhandledMatchError
错误,我们可以使用default
关键字来解决此问题:
$status = 3;
$ret = match($status) {
0 => '禁用',
1 => '启用1',
default => '未知', // 如果没有此分支,会抛出错误
};
var_dump($ret); // 输出:未知
2)Nullsafe运算符
在对象的链式调用中,如果中间任一环节返回了NULL
,会导致PHP报错:
$result = $repository->getUser(5)->name; // 如果getUser方法返回了NULL,就会报错
可以使用NullSafe运算符?->
来解决此问题:
$result = $repository?->getUser(5)?->name; // 不会报错,$result的值为NULL
3)命名参数
支持根据参数名传参,好处:可以跳过某些默认值参数,不必再传一遍。
function sum($a = 1, $b = 2, $c = 3)
{
return $a + $b + $c;
}
// 假设要传c参数过去,其它参数保持默认值不变
// php8前,a、b参数是必须传的,不能省略
sum(1, 2, 6);
// php8,可以只传c参数
sum(c: 6);
// 甚至顺序都可以打乱
sum(c: 6, a: 2, b: 3);
4)联合类型声明
使用|
来连接多个类型,它们之间是“或”的关系:
// $a的类型可以是int,或者float
function sum(int|float $a = 1, int|float $b = 2): float|int
{
return $a + $b;
}
5)新增mixed类型
mixed
代表任何类型,可以接受任何类型的值,是顶级类型。
function foo(mixed $str): mixed
{
}
6)对象可以通过::class获取类名
现在可以通过 $object::class
获取类名,返回的结果和 get_class($object)
一致。
$obj = new StdClass();
var_dump($obj::class); // 输出:stdClass
var_dump(get_class($obj)); // 输出:stdClass
7)构造器属性提升
类的构造器里的参数,如果带有可见性修饰符(private
/protected
/public
),那么这个参数会自动被添加为类的属性:
class Person
{
public function __construct(public string $name)
{
}
}
$obj = new Person('tim');
// 虽然没有显示定义name属性,但仍然可以访问
var_dump($obj->name); // 输出:tim
8)注解
新增注解功能。
PHP 8.1
官方文档
1)枚举类型
枚举类型Enum用于定义一组包含若干个可能值的集合,它本质上是一个类,可以像普通的class一样,定义自己的方法、实现接口,但是不支持继承、不支持new
实例化。
Enum分为纯粹Enum(Pure Enum)和回退Enum(Backed Enum)两种,定义:
// 纯粹Enum,没有case值
enum Pure
{
case One;
case Two;
}
// 回退Enum,有case值
// case值仅支持string和int两种类型
enum Backed: string
{
case One = 'A';
case Two = 'B';
}
// 访问单个case条目
var_dump(Pure::One);
// 获取Enum里的所有case条目
var_dump(Pure::cases()); // 返回的是一个数组
参数类型约束:
enum Status
{
case Pending;
case Completed;
case Failed;
}
function setStatus(Status $stat)
{
}
setStatus(Status::Pending); // 合法值,正常
setStatus('success'); // success不是Status枚举类型的合法值,报错
Enum的每个条目(case)都是该Enum的一个对象,并且有一个只读的name
属性:
// Pending是Status的一个对象
var_dump(gettype(Status::Pending)); // 输出:object
var_dump(Status::Pending instanceof Status); // 输出:true
// name属性的值等于case条目本身的名称
var_dump(Status::Pending->name); // 输出:Pending
回退Enum的条目有个额外的只读属性value
, 它是定义时指定的值:
enum Backed: string
{
case One = 'A';
case Two = 'B';
}
var_dump(Backed::One->value); // 输出:A
根据case值转换成Enum,可以使用from
或者tryFrom
方法:
enum Backed: string
{
case One = 'A';
case Two = 'B';
}
$val1 = 'A';
var_dump(Backed::from($val1)); // 输出:enum(Backed::One)
$val2 = 'C';
var_dump(Backed::from($val2)); // C不是合法值,报ValueError错误
var_dump(Backed::tryFrom($val2)); // 不会报错,输出:NULL
将枚举类型转换为value=>文本
数组,可以用于HTML选择框的展示:
enum UserStatus: int
{
case DISABLED = 3;
case NORMAL = 4;
/**
* 返回case条目对应的可读中文描述
*
* @return string
*/
public function text(): string
{
return match($this) {
self::DISABLED => '禁用',
self::NORMAL => '启用',
};
}
}
$map = [];
foreach (UserStatus::cases() as $case) {
$map[$case->value] = $case->text();
}
print_r($map); // 输出:Array([3] => 禁用 [4] => 启用)
2)Spread运算符支持展开关联数组
在PHP 7.4版本已经支持在数组内使用Spread运算符(...
)展开数组,但当时仅支持索引数组的展开,现8.1版本已支持关联数组:
$sub = ['c' => 'cat', 'd' => 'dog'];
$arr = ['a' => 'apple', ...$sub];
print_r($arr); // 输出:['a' => 'apple', 'c' => 'cat', 'd' => 'dog']
注意:如果有相同的key,后面的值会覆盖前面的值
3)新增never返回值类型
返回值声明为never
的函数,代表它永远都不会返回,因此不能包含return
语句。里面要么有调用exit()
结束脚本,要么抛出异常,要么永远不会终止(例如死循环)。
function foo(): never
{
exit(); // OK
while(true) {} // OK
throw new Exception('Oh my god'); // OK
}
4)readonly属性
可以使用 readonly 修饰符声明类属性,防止初始化后修改属性。
class Person
{
public readonly string $name;
public function setName(string $name): void
{
$this->name = $name;
}
}
$p = new Person();
$p->setName('First name'); // 第一次赋值,正常
var_dump($p->name); // 输出:First name
$p->setName('First name'); // 第二次赋值,报错:Uncaught Error: Cannot modify readonly property Person::$name
注意:
readonly
不能用于修饰静态属性- 只读属性只能初始化一次,并且只能从声明它的作用域内初始化。即使
clone
出来的新对象也不能重新初始化只读属性(自 PHP 8.3.0 起,可以重新初始化) - 只读属性不能设置默认值,因为具有默认值的只读属性等同于常量,因此不是特别有用
- 如果只读属性是一个对象或者资源,那么这个对象里的属性仍然是可变的
class Name
{
public string $firstName = 'first';
}
class Person
{
public readonly Name $nameObj;
public function setName(Name $obj): void
{
$this->nameObj = $obj;
}
}
$p = new Person();
$p->setName(new Name()); // 第一次赋值,正常
var_dump($p->nameObj->firstName); // 输出:first
$p->nameObj->firstName = 'new'; // 修改只读属性对象的内部属性,正常
var_dump($p->nameObj->firstName); // 输出:new
// 再次赋值,报错
$p->setName(new Name());
5)交集类型声明
使用&
来连接不同的类型,它们之间是“且”的关系:
// $val需满足既是UnitEnum类型,又是ArrayAccess类型
function foo(UnitEnum&ArrayAccess $val)
{
}
6)可以使用final来修饰类常量
使用final
关键字修饰类常量,防止被子类修改:
class Foo
{
final public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar"; // 不能修改父类中的final常量
}
// Fatal error: Bar::X cannot override final constant Foo::X
7)使用new初始化参数
参数、静态变量、甚至全局常量都可以直接初始化为对象了:
define('MY_CONSTANT', new \StdClass());
function foo(object $val = new \StdClass()): void
{
}
PHP 8.2
官方文档
1)新增true/false/null类型
function foo(null $a, true $b, false $c)
{
}
过时:动态属性
在旧版本中,如果尝试在 object
上赋值不存在的属性,PHP 将会自动创建相应的属性:
class Person {}
$obj = new Person();
$obj->name = 'xxx'; // 旧版本中,可以赋值不存在的属性
var_dump($obj->name);
自 PHP 8.2.0 起此特性被标记为过时(Deprecated)。建议更改为属性声明。要处理任意属性名称,类应该实现魔术方法 __get()
和 __set()
。最后可以使用 #[\AllowDynamicProperties]
注解标记此类。