php8的新特性 浅过一下

php8都出了这么久,还没仔细看过文档,知道一些常用特性,今天浅过一下官网着重介绍的一些知识点吧

8.0

https://www.php.net/releases/8.0/zh.php

命名参数

命名参数允许基于参数名称(而不是参数位置)将参数传递给函数。 这使得自变量的含义可以自动记录,使自变量与顺序无关,并允许任意跳过默认值。

这里就是对原来的参数,进行了命名,外部传入时,可无顺序的进行传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

function named_params(int $a, int $b, int $c = 0, int $d = 0)
{
echo "a:{$a} b:{$b} c:{$c} d:{$d}" . PHP_EOL;
}

named_params(1, 2, 3, 4); // a:1 b:2 c:3 d:4
named_params(a: 1, b: 2, c: 3, d: 4); // a:1 b:2 c:3 d:4
named_params(1, 2, c: 3, d: 4); // a:1 b:2 c:3 d:4

// 完全打乱顺序
named_params(d: 4, c: 3, b: 2, a: 1); // a:1 b:2 c:3 d:4

// 可选参数在必填参数前
//named_params(c: 3, d: 4, 1, 2); // PHP Fatal error: Cannot use positional argument after named argument

  • 完全打乱顺序的情况下,ide会有顺序的校验,虽然也能用,但是下划线看着不舒服,建议还是按顺序进行传参
  • 有了这个功能,对于一些有默认值的方法,可以只修改一个默认值,就比较舒服了,不用像以前那样还需要按顺序填上默认值
  • 最大的好处是传入参数的顺序是和定义无关的,而且还可以混合传参(但不建议)
  • 对于代码的可读性来说,有了命名,更加直观了

注解

可以用 PHP 原生语法来使用结构化的元数据,而非 PHPDoc 声明。

  • 之前要使用注解,是通过phpdoc去解析,强大的doctrine/annotations
  • 8之后支持原生注解啦,#[Attribute]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

#[Attribute]
class DemoAttribute1
{
public function __construct(public string $value)
{
}
}

#[Attribute]
class DemoAttribute2
{
public function __construct(public string $value)
{
}
}

#[DemoAttribute1(value: 'demo1')]
#[DemoAttribute2(value: 'demo2')]
class AttributeTest
{
}

$class = new ReflectionClass(AttributeTest::class);
$attributes = $class->getAttributes();

foreach ($attributes as $attribute) {
var_dump($attribute->getName());
var_dump($attribute->getArguments());
}

/*
string(14) "DemoAttribute1"
array(1) {
["value"]=>
string(5) "demo1"
}
string(14) "DemoAttribute2"
array(1) {
["value"]=>
string(5) "demo2"
}
*/
Attribute 说明
Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION 函数
Attribute::TARGET_METHOD 方法
Attribute::TARGET_PROPERTY 属性
Attribute::TARGET_CLASS_CONSTANT 类常量
Attribute::TARGET_PARAMETER 参数
Attribute::TARGET_ALL 所有
Attribute::IS_REPEATABLE 允许多次声明
  • Attribute::TARGET_CLASS

    1
    2
    #[Attr('foo')]
    class Example {}
  • Attribute::TARGET_FUNCTION

    1
    2
    3
    4
    5
    6
    7
    8
    #[Attr('foo')]
    function example(){}

    $fn = #[Attr('foo')] fn() => 1 > 2;

    $fn = #[Attr('foo')] function() {
    return 1 > 2;
    }
  • Attribute::TARGET_METHOD

    1
    2
    3
    4
    5
    class Foo 
    {
    #[Attr('bar')]
    public function bar(){}
    }
  • Attribute::TARGET_PROPERTY

    1
    2
    3
    4
    class Foo {
    #[Attr('foo')]
    private string $foo;
    }
  • Attribute::TARGET_CLASS_CONSTANT

    1
    2
    3
    4
    class Foo {
    #[Attr('foo')]
    private const FOO = 'foo';
    }
  • Attribute::TARGET_PARAMETER

    1
    function example(#[Attr('foo')] string $foo) {}

构造器属性提升

构造器的参数也可以相应提升为类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

class Point
{
public function __construct(protected int $x, protected int $y = 0) {
}

public function getX(): int
{
return $this->x;
}

public function getY(): int
{
return $this->y;
}
}

$point = new Point(1, 2);
var_dump($point->getX()); // int(1)
var_dump($point->getY()); // int(2)

联合类型

联合类型接受多个不同的简单类型做为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class Number {
public function __construct(private float|int $number) {}

public function getNumber(): float|int
{
return $this->number;
}
}

$number = new Number(1);
var_dump($number->getNumber()); // int(1)
$number = new Number(1.1);
var_dump($number->getNumber()); // float(1.1)
  • 但随着php越来越规范化,个人建议少用联合类型

Match 表达式

类似于 switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

$foo = 'function foo';

function foo(): string
{
var_dump('test function');
return 'function foo';
}

$result = match ($foo) {
'bar' => 'bar',
1 => '1',
true => true,
foo(), 'foo' => 'function foo',
default => 'default'
};

var_dump($result);
// string(13) "test function"
// string(12) "function foo"
  • match 比较分支值,使用了严格比较 (===), 而 switch 语句使用了松散比较。
  • match 表达式会返回一个值。
  • match 的分支不会像 switch 语句一样, 落空时执行下个 case。
  • match 表达式必须彻底列举所有情况。
  • 逐个检测匹配分支,只会执行返回的表达式所对应的匹配条件表达式

Nullsafe 运算符

现在可以用新的 nullsafe 运算符链式调用,而不需要条件检查 null。 如果链条中的一个元素失败了,整个链条会中止并认定为 Null。

1
2
3
4
5
6
7
8
9
10
11
12
13
$country =  null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();

if ($address !== null) {
$country = $address->country;
}
}
}
// php8
$country = $session?->user?->getAddress()?->country;

字符串与数字的比较更符合逻辑

PHP 8 比较数字字符串(numeric string)时,会按数字进行比较。 不是数字字符串时,将数字转化为字符串,按字符串比较。

1
2
3
4
5
6
7
8
0 == 'foobar' // false

// Before | After | Type
var_dump(42 == " 42"); // true | true | well-formed
var_dump(42 == "42 "); // true | false | non well-formed (*)
var_dump(42 == "42abc"); // true | false | non well-formed
var_dump(42 == "abc42"); // false | false | non-numeric
var_dump( 0 == "abc42"); // true | false | non-numeric

内部函数类型错误的一致性

现在大多数内部函数在参数验证失败时抛出 Error 级异常。

1
2
3
4
5
6
7
// php 7
strlen([]); // Warning: strlen() expects parameter 1 to be string, array given
array_chunk([], -1); // Warning: array_chunk(): Size parameter expected to be greater than 0

// php 8
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0

JIT

https://www.laruence.com/2020/06/27/5963.html

// todo 学习

8.1

https://www.php.net/releases/8.1/zh.php

枚举

终于不用常量来定义枚举了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php

/**
* 只能包含方法、静态方法。不允许包含属性,包含属性的 trait 会导致 fatal 错误。
*/
trait GetValues
{
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}

/**
* 枚举可以实现interface,不能abstract
*/
interface Hello
{
public function hello(): string;
}

/**
* 禁止构造、析构函数。
* 不支持继承。无法 extend 一个 enum。
* 不支持静态属性和对象属性。
* 由于枚举条目是单例对象,所以不支持对象复制。
* 除了下面列举项,不能使用魔术方法。
* enum 可以 implement 任意数量的 interface。
* 枚举和它的条目都可以附加 注解。 目标过滤器 TARGET_CLASS 包括枚举自身。 目标过滤器 TARGET_CLASS_CONST 包括枚举条目。
* 不能用 new 直接实例化枚举条目, 也不能用 ReflectionClass::newInstanceWithoutConstructor() 反射实例化。
*/
enum Suit: string implements Hello
{
use GetValues;

case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
// case Spades1 = 'S'; error 不能有重复的回退值,一定是唯一的

const H = 1;
const D = 1;
const C = 1;
const S = 1;

public function hello(): string
{
return 'hello Suit.' . $this->name;
}

public function getValue(): string
{
return $this->value;
}

public static function fromLength(int $cm): Suit
{
return match ($cm) {
Suit::H => Suit::Hearts,
Suit::D => Suit::Diamonds,
Suit::C => Suit::Clubs,
Suit::S => Suit::Spades,
};
}
}

var_dump(Suit::cases());
// array(4) {
// [0]=>
// enum(Suit::Hearts)
// [1]=>
// enum(Suit::Diamonds)
// [2]=>
// enum(Suit::Clubs)
// [3]=>
// enum(Suit::Spades)
// }

var_dump(Suit::values());
// array(4) {
// [0]=>
// string(1) "H"
// [1]=>
// string(1) "D"
// [2]=>
// string(1) "C"
// [3]=>
// string(1) "S"
// }

var_dump(Suit::Hearts->hello()); // string(17) "hello Suit.Hearts"

var_dump(Suit::Hearts->getValue()); // string(1) "H"
var_dump(Suit::fromLength(Suit::H)); // enum(Suit::Hearts)
var_dump(Suit::tryFrom('C')); // enum(Suit::Clubs)
var_dump(serialize(Suit::Diamonds)); // string(21) "E:13:"Suit:Diamonds";"
var_dump(Suit::Hearts === unserialize(serialize(Suit::Hearts))); // bool(true)

function query(Suit $suit)
{
var_dump($suit);
}
query(Suit::Clubs); // enum(Suit::Clubs)

只读属性

只读属性不能在初始化后更改,即在为它们分配值后。它们可以用于对值对象和数据传输对象建模。

1
2
3
4
5
6
7
8
9
10
11
12
<?php

class Test
{
public function __construct(public readonly string $prop)
{
}
}

$test = new Test('123');
var_dump($test->prop); // string(3) "123"
// $test->prop = 456;// Fatal error: Uncaught Error: Cannot modify readonly property Test::$prop

First-class 可调用语法

First-Class callables是一种引用闭包和函数的新方法,就是对函数的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

//$fn = Closure::fromCallable('strlen');
$fn = strlen(...);
var_dump($fn('yes')); // int(3)

class Demo
{
public function method(string $key)
{
var_dump(__FUNCTION__.":{$key}");
}

public static function staticMethod(string $key)
{
var_dump(__FUNCTION__.":{$key}");
}

public function run(){
// $fn = Closure::fromCallable([$this, 'method']);
$fn = $this->method(...);
$fn('run');
}
}
(new Demo())->run(); // string(10) "method:run"


//$fn = Closure::fromCallable([Demo::class, 'staticMethod']);
$fn = Demo::staticMethod(...);
$fn('run'); // string(16) "staticMethod:run"

新的初始化器

对象现在可以用作默认参数值、静态变量和全局常量,以及属性参数。
这有效地使使用 嵌套属性 成为可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

interface LoggerInterface
{
public function log(string $message): void;
}

class NullLogger implements LoggerInterface
{
public function log(string $message): void
{
// 什么也不用做
}
}

class EchoLogger implements LoggerInterface
{
public function log(string $message): void
{
echo $message . PHP_EOL;
}
}

class Service
{
public function __construct(
protected LoggerInterface $logger = new NullLogger(),
) {
}

public function run()
{
$this->logger->log('run');
}
}

$service1 = new Service();
$service1->run(); //

$service2 = new Service(new EchoLogger());
$service2->run(); // run

纯交集类型

当一个值需要同时满足多个类型约束时,使用交集类型。
注意,目前无法将交集和联合类型混合在一起,例如 A&B|C。

1
2
3
4
5
6
7
function count_and_iterate(Iterator&Countable $value) {
foreach ($value as $val) {
echo $val;
}

count($value);
}

Never 返回类型

never 是一种表示没有返回的返回类型。这意味着它可能是调用 exit(), 抛出异常或者是一个无限循环。所以,它不能作为联合类型的一部分。

1
2
3
4
5
6
7
8
9
function redirect(string $uri): never {
header('Location: ' . $uri);
exit();
}

function redirectToLoginPage(): never {
redirect('/login');
echo 'Hello'; // <- dead code detected by static analysis
}

Final 类常量

可以声明 final 类常量,以禁止它们在子类中被重写。

1
2
3
4
5
6
7
8
9
class Foo
{
final public const XX = "foo";
}

class Bar extends Foo
{
public const XX = "bar"; // Fatal error
}

显式八进制数字表示法

现在可以使用显式 0o 前缀表示八进制数。

1
2
0o16 === 16; // false — not confusing with explicit notation
0o16 === 14; // true

纤程

https://www.php.net/manual/zh/language.fibers.php
纤程可以在调用堆栈中的任何位置被挂起,在纤程内暂停执行,直到稍后恢复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

$fiber = new Fiber(function (int $one): int {
$two = Fiber::suspend($one);
$three = Fiber::suspend($two);
$four = Fiber::suspend($three);
$five = Fiber::suspend($four);
return Fiber::suspend($five);
});

print $fiber->start(1) . PHP_EOL; // 1

var_dump($fiber->isRunning()); // bool(false)
var_dump($fiber->isStarted()); // bool(true)
var_dump($fiber->isSuspended()); // bool(true)
var_dump($fiber->isTerminated()); // bool(false)

print $fiber->resume(2) . PHP_EOL; // 2
print $fiber->resume(3) . PHP_EOL; // 3
print $fiber->resume(4) . PHP_EOL; // 4
print $fiber->resume(5) . PHP_EOL; // 5
print $fiber->resume(6) . PHP_EOL; //
print $fiber->getReturn() . PHP_EOL; // 6

var_dump($fiber->isTerminated()); // bool(true)

对字符串键控数组的数组解包支持

PHP 以前支持通过扩展运算符在数组内部解包,但前提是数组具有整数键。现在也可以使用字符串键解包数组

1
2
3
4
5
6
7
8
9
10
11
// php < 8.1
$arrayA = ['a' => 1];
$arrayB = ['b' => 2];
$result = array_merge(['a' => 0], $arrayA, $arrayB);
// ['a' => 1, 'b' => 2]

// php 8.1
$arrayA = ['a' => 1];
$arrayB = ['b' => 2];
$result = ['a' => 0, ...$arrayA, ...$arrayB];
// ['a' => 1, 'b' => 2]