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

做軟體的禪師發表於2020-10-28

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

2020年10月29日,PHP8將釋出 RC3的版本,11月26日將正式釋出。

下面每個新特性介紹,都是禪師閱讀過相關的RFC說明後,加入了禪師的理解說明,還擴充了一些易理解的程式碼,不要理解為簡單的譯文哦,讓我們一起通過實際程式碼,看看PHP8都有哪些變化。

這裡的 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不同於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) {
// }
<?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

物件語言多型的特性,這裡我覺得叫 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;
    }
}

原來我們返回型別名,需要呼叫 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”

這個是針對字串連續性的語法問題,仔細研究後,感覺還存在一些問題,等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);

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
<?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 { }
}

直接看下面程式碼中的差異:

<?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,
);

以前必須定義 $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 是一種混合型別,等同於 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 表示式 和swtich 條件分支類似,主要作用是直接返回值。

<?php
declare(strict_types=1);

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

這個改動很好,不過有點參照 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的釋出值得期待。

本文由禪師原創,團隊長期招募 Laravel,Vue.js,Uniapp/Cordova/Electron 技術棧的人才,至少符合1項的小夥伴歡迎加我嶶x zencodex,除了平時一起交流學習,有專案或者遠端offer的時候,可以隨時合作。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章