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 声明。
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)
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]