在 PHP 8.0 發佈一年後,PHP 8.1 在今年 2021 年 11 月 25 日也發佈了。
去年我曾寫了一篇文章,介紹 PHP 8.0,而過去這一整年,我在開發上也實際使用了不少 PHP 8.0 的新功能,例如 Nullsafe operator
、match
及 Constructor property promotion
。
這些改進跟新功能,替開發帶來不少方便跟效率。我相信這次的 8.1 版本,PHP 團隊也會帶來不少令人驚豔的地方吧!
本篇一樣會介紹一些我感興趣的新功能,搭配一些簡單的試玩。大家可以參考看看。
PHP 8.1 新功能介紹
Enumerations 枚舉
以前要列舉一些狀態,只能利用類別 Class
搭配常數 const
使用,但是新的 Enumerations
改善了這個情況。
過去使用類別↓
class Color
{
const RED = 'RED';
const BLUE = 'BLUE';
const GREEN = 'GREEN';
}
function setColor(string $color)
{
print $color;
}
現在利用枚舉↓
enum Color
{
case RED;
case BLUE;
case GREEN;
}
function setColor(Color $color)
{
print $color->name;
}
使用 enum
定義一些值,這些值可以被歸納成同一個類型,之後只要指定傳入參數為這個類型的 enum
,就可以透過 PHP 來幫你把關。
setColor(Color::RED);
// ok
setColor('RED');
// PHP Fatal error:
// Uncaught TypeError: setColor():
// Argument #1 ($color) must be of type ColorEnum, string given
不過這個乍看很簡單的新功能,再我稍微讀了一下 Enumerations 的文件後,才發現水還蠻深的,有興趣的人可以看看。
Readonly Properties 唯讀屬性
class Student
{
public readonly string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function setName($newName)
{
$this->name = $newName;
}
}
只能在建構子 __construct
中決定 readonly
的值。一旦初始化了就不能再改,不論是在內部或外部。
$s = new Student('Tom');
$s->name = 'Jack';
// PHP Fatal error:
// Uncaught Error: Cannot modify readonly property Student::$name
$s->setName('Jack');
// PHP Fatal error:
// Uncaught Error: Cannot modify readonly property Student::$name
readonly
是執行期決定的,同個類別中的每個物件可以有不一樣的值。但是 const
是定義的時候就寫好的,同個類別下個每個物件看到的都會一樣。
要注意的是,readonly
不能設定預設值,因為這樣就等同於 const
。而目前也不支援 static
。
另外,可能是因為新的特性的關係,所以比較嚴格,readonly
被規定只能用在 typed properties
,也是必須要明確定義它的型別,如範例 public readonly string $name
中 的 string
。
First-class Callable Syntax
這個東西有點微妙。有一種開發上的特殊需求是把 callable
轉換成 Closure
。
比如說,strlen
是 PHP 內建的函式,它是一種 callable
,在過去要把它轉換成 Closure
時,需要透過 Closure::fromCallable
,但現在可以透過直接在函式名稱後面加上 (...)
的方式,來達到一樣的效果。
$strlenClosure = Closure::fromCallable('strlen');
var_dump($strlenClosure);
$strlenClosure = strlen(...);
var_dump($strlenClosure);
兩個的輸出都是:
object(Closure)#1 (1) {
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
有點微妙吧,一般人可能也不太有把 callable
轉成 Closure
的需求。不過我在查資料的時候找到這篇文章,它說明了需要轉換的情境,我覺得還不錯,有興趣的可以參考看看:The new Closure::fromCallable()
in PHP 7.1。
New in initializers
這也是一個增加便利性的功能,以往在參數的預設值,沒辦法用物件,因為物件需要透過 new
來實體化。但是從 8.1
開始可以了。
class Cellphone
{
public function __construct(
protected string $type
) {}
}
class Student
{
public function __construct(
protected Cellphone $cellphone = new Cellphone('iPhone')
) { }
public function setCellphone(
Cellphone $cellphone = new Cellphone('ZenFone')
) {
$this->cellphone = $cellphone;
}
};
在沒有傳入任何參數的情況下,就會直接使用預設的 new Cellphone('iPhone')
。
$student = new Student();
另外也特別測試了一下在非建構子的 function 能不能使用 new
,答案是可以。
$student->setCellphone();
使用 setter
卻給預設值,是有點怪,不過這只是為了實驗,請大家將就一下。setter
的合理使用方式是這樣:
$student->setCellphone(new Cellphone('Google Pixel'));
Pure Intersection Types 純交集類別
(這個翻譯有點怪XD 不是很確定 pure 在這邊怎麼翻比較好)
各位不曉得還記不記得 PHP 8.0 新增了 Union Type,聯集類型,只要符合其中一個都可以。而交集類型呢,則必須要都符合才可以。
interface Interface_1 {};
interface Interface_2 {};
class A implements Interface_1, Interface_2{};
class B implements Interface_1{};
function test(Interface_1&Interface_2 $foo) {}
透過 &
來把所有需要符合的類型都串接起來。
test(new A);
// ok
test(new B);
// PHP Fatal error: Uncaught TypeError: test():
// Argument #1 ($foo) must be of type Interface_1&Interface_2, B given
雖然有點瘋狂,但想要串接幾個都可以。
interface Interface_1 {};
interface Interface_2 {};
interface Interface_3 {};
interface Interface_4 {};
class A implements Interface_1, Interface_2, Interface_3 {};
function test(Interface_1&Interface_2&Interface_3&Interface_4 $foo) {}
test(new A);
我認為這個新特性最大的好處就是,我們可以不用在類別內在透過 instanceof
的方式,一個一個檢查必須要符合的類別了。
Never return type 從不返回類型
又是一個怪怪的翻譯。never return type
用來宣告 function
的回傳值,表示這個 function
不會 return
,可能是離開了,像是呼叫了 exit()
、拋了錯誤,或是一個無窮迴圈。
實際測試,這樣的情況會跳出錯誤訊息:
function testNever(): never {
echo 'hello world';
}
testNever();
// PHP Fatal error:
// Uncaught TypeError: testNever():
// never-returning function must not implicitly return
對,沒有呼叫 return
也是一種返回。必須要明確地離開才可以。
function testNever(): never {
echo 'hello world';
exit();
}
testNever(); // ok
Final class constants 最後類別常數
恩,一個很清楚明瞭的特性。常數宣告為 final
就不能被子類別複寫。
class A
{
final public const TEST = "TEST_A";
}
class B extends A
{
public const TEST = "TEST_B";
}
錯誤訊息:
PHP Fatal error: B::TEST cannot override final constant A::TEST
Array unpacking support for string-keyed arrays
在 8.1 可以把以 string
作為 key 的陣列打開了。(unpacking 不曉得怎麼翻譯比較對,暫時翻譯成打開。)
所以根據官網範例,可以更簡單的做到陣列的合併。
$a = ['a' => 'Apple', 'b' => 'Banana'];
$b = ['b' => 'Melon', 'c' => 'Orange'];
$c = [...$a, ...$b];
// $C 等同於 ['a' => 'Apple', 'b' => 'Melon', 'c' => 'Orange'];
PHP 8.1 效能提升
前面介紹了一些 PHP 8.1 的新特性跟功能,除此之外,PHP 8.1 效能好像也提升了一點。
根據官網說明,相較於 8.0,8.1的測試結果:
- Symfony Demo 加速了 23.0%
- WordPress 加速了 3.5%
另外,秉持著實驗家精神,我自己也實測了一下 PHP 8.1 的速度。可以參考這篇文章:
PHP 8.1、8.0、7.4 效能實測結果,Laravel 8 速度提升了 15%
PHP 8.2 在 2022 年 12 月底釋出了,有興趣者可以前往這邊觀看。