PHP8 都有哪些新功能,说说PHP8的新增特性

PHP8 都有哪些新功能,说说PHP8的新增特性

2020年10月29日,PHP8将发布 RC3的版本,11月26日将正式发布。

新增 union types

这里的 union 和c/c++的不同,是指PHP支持多类型定义,比如例子里的 $number 同时支持整型(int) 和 浮点型(float)。

如果这样使用 $number->setNumber(‘string’),将会引发错误提示。所以强类型定义的好处是提前在代码编译期就能发现错误,从而避免代码错误赋值。

当然这是强类型语言的特性,因为减少类型处理的麻烦,自然可以提升运行速度。这也是为啥强类型语言比脚本语言快的根本原因之一。如下面示例代码:

<?php
declare(strict_types=1);

class Number {
    private int|float $number;

    public function setNumber(int|float $number): void {
        $this->number = $number;
    }

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

/**
 * We can pass both floats or integer values
 * to the number object. Try passing a string.
 */
$number = new Number();

$number->setNumber(5);

dump($number->getNumber());

$number->setNumber(11.54);

dump($number->getNumber());

输出:

5

11.54

WeakMap 弱键值引用

WeakMap不同于PHP7.4引入的Weak References(弱类型引用)。

当一个对象作为WeakMap的 Key 时,对象销毁时,Key 会自动从 WeakMap 里移除。

如下面示例代码:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);
// object(WeakMap)#1 (1) {
//   [0]=>
//   array(2) {
//     ["key"]=>
//     object(stdClass)#2 (0) {
//     }
//     ["value"]=>
//     int(42)
//   }
// }

// The object is destroyed here, and the key is automatically removed from the weak map.
unset($obj);
var_dump($map);
// object(WeakMap)#1 (0) {
// }

新增 ValueError 内部异常

<?php
declare(strict_types=1);

/**
 * array_rand 第一个参数不能是空数组,否则会引发 ValueError
 */
array_rand([], 0);

/**
 * 第三个参数 depth 必须是大于0的整数,否则会引发 ValueError
 */
json_decode('{}', true, -1);

错误输出:

ValueError array_rand(): Argument #1 ($array) cannot be empty

方法重写(overriding)里允许可变参数

对象语言多态的特性,这里我觉得叫 overloading(重载)更好点。因为保持原有参数不变的情况下,继承类中使用才叫 overriding。

原有PHP的多态没有c++/Java完善,这里就不咬文嚼字了,大家理解含义就好。这个新特性也恰恰弥补了这点。

三个点开头 …$param 这种叫可变参数,这个特性见如下代码:

<?php
declare(strict_types=1);

class A {
    public function method(int $many, string $parameters, $here) {

    }
}
class B extends A {
    public function method(...$everything) {
        dd($everything);
    }
}

$b = new B();
$b->method('i can be overwritten!');

输出:

array:1 [▼

0 => “i can be overwritten!”

]

返回静态类型

这个不是什么好玩的新特性,应该是针对 static语法冲突的解决方案。如果我理解有误,欢迎指正。先看这个代码:

class A {
    public function test(static $a) {}
}
class B extends A {}

function call_with_new_a(A $a) {
    $a->test(new A);
}

call_with_new_a(new B);
class A {
    // Is this an untyped static property,
    // or an instance property of type static?
    public static $a;
}

注意上面两段代码就是存在 static 冲突的代码。是无效代码,因为PHP8为了解决冲突,仅允许在函数返回类型中使用 static修饰,含义为返回当前类的定义。正确使用方法如下:

<?php
declare(strict_types=1);

class Test {
    public function doWhatever(): static {
        // Do whatever.
        return $this;
    }
}

$object::class返回类型名的语法糖

原来我们返回类型名,需要调用 get_class($object),PHP8新增了一个语法糖 $object::class,是比以前简单了很多,看来未来有可能删除掉 get_class。

<?php
declare(strict_types=1);

auth()->loginUsingId(1);

dump(auth()->user()::class);

// Or with a temporary variable
$user = auth()->user();

dump($user::class);

输出:

“App\User”
“App\User”

变量语法上的微调(Variable Syntax Tweaks)

这个是针对字符串连续性的语法问题,仔细研究后,感觉还存在一些问题,等PHP8正式发版再看看。或者我理解有误的地方,也欢迎指正。

/**
 * Variable Syntax Tweaks 
 * New and instanceof can now be used with arbitrary expressions, 
 * using new (expression)(...$args) and $obj instanceof (expression).
 */
<?php

class Foo {}

$f = 'Foo';
new $f;   // PHP7 需要个中间变量才合法

new 'Foo'; // PHP7 不合法,PHP8里变合法了

// PHP8的这个特性,目前根据RFC,以下代码还是存在一些问题,等待正式发布再看看

echo __FUNCTION__[0]; // RFC文档说明应该合法,但还是不合法

$k = 'o';
new "F$k$k";  // RFC文档说明应该合法,但还是不合法

下面代码在PHP8中合法:

<?php
declare(strict_types=1);

class Foo {}
class Bar {}

$class = new (collect(['Foo', 'Bar'])->random());

dd($class);

Stringable interface

PHP8引入了 Stringable,当一个类实现 __toString 时,类会自动转成 Stringable 的实例,而不需要显式声明,代码如下:

<?php
declare(strict_types=1);

class Foo {
    public function __toString() {
        return 'I am a class';
    }
}

$obj = new Foo;
dump($obj instanceof Stringable);  // 输出 true

Traits 新增 abstract private 方法

<?php
declare(strict_types=1);

trait MyTrait {
    abstract private function neededByTheTrait(): string;

    public function doSomething() {
        return strlen($this->neededByTheTrait());
    }
}

class TraitUser {
    use MyTrait;

    // This is allowed:
    private function neededByTheTrait(): string { }

    // This is forbidden (incorrect return type)
    // private function neededByTheTrait(): stdClass { }

    // This is forbidden (non-static changed to static)
    // private static function neededByTheTrait(): string { }
}

throw 可以作为表达式使用了

直接看下面代码中的差异:

<?php
declare(strict_types=1);

// 下面代码在PHP7中不合法,在PHP8中合法了
$callable = fn() => throw new Exception();

$nullableValue = null;

// $value is non-nullable.
$value = $nullableValue ?? throw new \InvalidArgumentException();

允许最后一个参数后面的逗号了 ,

直接看代码,$d, 4, 后面都允许有多余的逗号了:

<?php
declare(strict_types=1);

function method_with_many_arguments(
    $a, 
    $b,
    $c,
    $d,
) {
    dump("this is valid syntax");
}

method_with_many_arguments(
    1,
    2,
    3,
    4,
);

Catch 异常表达式,可以不定义变量了

以前必须定义 $e,像这样 catch($e \Exception),PHP8中可以不需要定义$e 了。

<?php
declare(strict_types=1);

$nullableValue = null;

try {
    $value = $nullableValue ?? throw new \InvalidArgumentException();
} catch (\InvalidArgumentException) {
    dump("Something went wrong");
}

新增 mixed 类型

mixed 是一种混合类型,等同于 array|bool|callable|int|float|null|object|resource|string。

<?php
declare(strict_types=1);

function debug_function(mixed ...$data) {
    dump($data);
}

debug_function(1, 'string', []);

输出:

array:3 [▼

0 => 1

1 => “string”

2 => []

]

新增属性定义

这个是PHP8比较大的改动,新语法 #[Attribute] 定义属性。以前操作需要使用反射,现在变简单了许多。代码后面使用 ReflectionClass 来验证 *#[xxxx] *这种语法。

<?php
declare(strict_types=1);
// First, we need to define the attribute. An Attribute itself is just a plain PHP class, that is annotated as an Attribute itself.

#[Attribute]
class ApplyMiddleware
{
    public array $middlware = [];

    public function __construct(...$middleware) {
        $this->middleware = $middleware;
    }
}

// This adds the attribute to the MyController class, with the "auth" middleware as an argument.

#[ApplyMiddleware('auth')]
class MyController
{
    public function index() {}
}

// We can then retrieve all ApplyMiddleware attributes on our class using reflection
// And read the given middleware arguments.

$reflectionClass = new ReflectionClass(MyController::class);

$attributes = $reflectionClass->getAttributes(ApplyMiddleware::class);

foreach ($attributes as $attribute) {
    $middlewareAttribute = $attribute->newInstance();
    dump($middlewareAttribute->middleware);
}

新语法:构造函数里添加属性

<?php
declare(strict_types=1);

class User {
    public function __construct(
        public int $id,
        public string $name,
    ) {}
}

$user = new User(1, 'Marcel');

dump($user->id);
dump($user->name);

新增 match 表达式

match 表达式 和swtich 条件分支类似,主要作用是直接返回值。

<?php
declare(strict_types=1);

echo match (1) {
    0 => 'Foo',
    1 => 'Bar',
    2 => 'Baz',
};

新增 nullsafe 操作符

这个改动很好,不过有点参照 typescript 和 swift 的影子。管他呢,好用就行,这个语法糖漂亮多了。

<?php
declare(strict_types=1);

class User {
    public function getAddress() {}
}

$user = new User();

$country = $user?->getAddress()?->country?->iso_code;

dump($country);

新增命名参数

python 的影子,管他呢,好用就行,这个特性也非常实用,不用再考虑必须保证参数顺序了,每个参数做啥的也能一目了然。

// 原来必须这样按顺序
array_fill(0, 100, 50);

// PHP8有了命名参数后,可以这样了
array_fill(start_index: 0, num: 100, value: 50);
array_fill(value: 50, num: 100, start_index: 0);

总结

以上关于PHP8的新特性,虽然没有介绍JIT相关,但比如针对强类型上的改进,union type, mixed, Weakmap等也是为了适应JIT的变化。

也有一些语法上的改进,尤其最后介绍的 nullsafe和命名参数,都是非常实用的。PHP8的发布值得期待。

本作品采用《CC 协议》,转载必须注明作者和本文链接
不住念,无名无相
本帖由系统于 3年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 11

新版的 match 相对于老版的 switch 的严格版。也就是说这玩意其实是严格比较key,不仅比较值还比较类型。具体可参考如下链接:

www.laruence.com/2020/07/13/6033.h...

3年前 评论

nullsafe挺实用的

3年前 评论

只能帮你喊个 666 大佬牛逼

3年前 评论

搞的不错!

3年前 评论

nullsafe 操作符 这个例子是什么意思?

3年前 评论
justmd5 3年前
晓鹤 3年前

@609468798

$user?->getAddress()?->country?->iso_code;这种链式调用,不用担有空值null,造成出现程序异常。

只有要空值的,结果也会返回空值。所以叫 nullsafe

3年前 评论

#新增命名参数 因为这个几度想搞python,现在可以安心了#新增命名参数 因为这个几度想搞python,现在可以安心了

3年前 评论
DonnyLiu

楼主~,match表达式的详解可以鸟哥这篇Blogs应该比较详细了,www.laruence.com/2020/07/13/6033.h...

3年前 评论

@做软件的禅师 你这个例子 原来我们 $user->getAddress()->country->iso_code; 这样取值 iso_code 现在改成 $user?->getAddress()?->country?->iso_code; 的语法取值iso_code 这样用的吗?

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!