文章目錄
- 前言
- PHP 7.0
- 1)NULL合并運算符:??
- 2)參數、返回值支持類型聲明
- 3)太空船操作符:<=>
- 4)通過 define 定義常量數組
- 5)匿名類實例化
- 6)字符串里使用\u轉義unicode codepoint
- PHP 7.1
- 1)數組解構賦值
- 2)可為空(Nullable)類型
- 3)新增void返回值類型
- 4)類常量可見性
- 5)多異常捕獲處理
- PHP 7.2
- 1)新增object類型
- 2)Sodium成為核心擴展
- PHP 7.3
- 1)數組解構支持引用賦值
- PHP 7.4
- 1)箭頭函數
- 2)NULL合并賦值運算符:??=
- 3)類屬性支持類型聲明
- 4)在數組中使用Spread運算符
- PHP 8.0
- 1)match表達式
- 2)Nullsafe運算符
- 3)命名參數
- 4)聯合類型聲明
- 5)新增mixed類型
- 6)對象可以通過::class獲取類名
- 7)構造器屬性提升
- 8)注解
- PHP 8.1
- 1)枚舉類型
- 2)Spread運算符支持展開關聯數組
- 3)新增never返回值類型
- 4)readonly屬性
- 5)交集類型聲明
- 6)可以使用final來修飾類常量
- 7)使用new初始化參數
- PHP 8.2
- 1)新增true/false/null類型
- 過時:動態屬性
前言
本文用于記錄PHP各個版本的新特性,僅記錄個人認為比較重要的特性,如果需要全面的信息,請查閱官方文檔。
PHP 7.0
官方文檔
1)NULL合并運算符:??
NULL合并運算符(Null coalescing operator)是個語法糖,用于簡化三元表達式和isset()
的寫法。如果變量存在且值不為 NULL
,就會返回自身的值,否則返回第二個操作數。
// php7前
$uid = isset($_GET['uid']) ? $_GET['uid'] : 0;// 使用NULL合并運算符
$uid = $_GET['uid'] ?? 0;// 鏈式使用,如果沒有a則獲取b,如果沒有b則返回默認值0
$uid = $_GET['a'] ?? $_GET['b'] ?? 0;
2)參數、返回值支持類型聲明
類型聲明可以用于函數的參數、返回值, PHP 7.4.0 起還可以用于類的屬性, PHP 8.3.0 起還可以用于類的常量。
function foo(string $str): string
{return $str . 'hello';
}
如果參數類型和實際傳的不一致,在可以轉換的前提下,PHP會自動進行一個隱式的轉換,如果不可以轉換,則會拋出一個 TypeError 錯誤。例如:
function foo(string $a): void
{var_dump($a); // 輸出:string(3) "123"
}foo(123); // 傳入int,會自動轉換為string
foo([1]); // 數組無法轉換為string,報錯
如果不希望自動轉換,可以開啟嚴格模式。在嚴格模式下,只能接受完全匹配的類型,否則會拋出 TypeError錯誤,唯一的例外是 int 值也可以傳入聲明為 float 的類型。
declare(strict_types=1); // 開啟嚴格模式function foo(string $a): void
{var_dump($a);
}foo(123); // 會直接報錯
3)太空船操作符:<=>
太空船操作符(Spaceship operator)用于比較兩個值的大小,如果相等返回0,如果左值小于右值返回-1,如果左值大于右值返回1
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
4)通過 define 定義常量數組
define('ANIMALS', ['dog','cat','bird'
]);
5)匿名類實例化
interface Logger
{public function save();
}$app->setLogger(new class implements Logger {public function save(){// TODO: Implement save() method.}}
);
6)字符串里使用\u轉義unicode codepoint
// 漢字“嚴”的unicode codepoint是4e25(十六進制)
echo "\u{4e25}" . PHP_EOL; // 輸出:嚴
PHP 7.1
官方文檔
1)數組解構賦值
數組解構(Array destructuring)是一種便捷的方式,用于將數組中的元素賦值給變量,這在處理數組或從函數返回多個值時特別有用。在PHP中,可以使用[]
語法進行解構。
關聯數組:
$data = ['name' => 'tim','gender' => 'male','age' => 18,
];
// 解構賦值
['name' => $name, 'age' => $age] = $data;var_dump($name, $age); // 輸出:tim, 18
索引數組:
$data = ['A', 'B', 'C'];[$a, $b] = $data; // 獲取索引為0和1的元素[$arr[], $arr[]] = $data; // 將索引0和1的元素放入到$arr數組[, , $c] = $data; // 如果不提供變量名,則忽略對應位置的元素[2 => $c] = $data; // 獲取索引為2的元素
在循環中使用解構:
$data = [['id' => 1, 'name' => 'tim'],['id' => 2, 'name' => 'john'],
];foreach ($data as ['name' => $name]) {var_dump($name);
}
2)可為空(Nullable)類型
參數以及返回值的類型現在可以通過在類型前加上一個問號使之允許為null
:
// $str參數要么為字符串,要么為null
function foo(?string $str): ?string
{
}
3)新增void返回值類型
返回值類型為void
的,不能返回任何數據,包括null
。
function foo(): void
{return; // 合法return null; // 不合法
}
試圖去獲取一個 void 方法的返回值會得到 null ,并且不會產生任何警告。
4)類常量可見性
可以為類中的常量設置可見性:
class ConstDemo
{const PUBLIC_CONST_A = 1; // 默認為publicpublic const PUBLIC_CONST_B = 2;protected const PROTECTED_CONST = 3;private const PRIVATE_CONST = 4;
}
5)多異常捕獲處理
一個catch
語句塊現在可以通過管道字符(|
)來實現多個異常的捕獲。 這對于需要同時處理來自不同類的不同異常時很有用。
try {// some code
} catch (FirstException | SecondException $e) {// handle first and second exceptions
}
PHP 7.2
官方文檔
1)新增object類型
function foo(object $obj): object
{
}
2)Sodium成為核心擴展
現代 Sodium 加密類已經成為 PHP 核心擴展。
PHP 7.3
官方文檔
1)數組解構支持引用賦值
$arr = ['tim' => ['uid' => 1],'john' => ['uid' => 2],
];['tim' => &$tim] = $arr;
$tim['uid'] = 3; // 此處的修改會影響到$arr
var_dump($arr);
PHP 7.4
官方文檔
1)箭頭函數
箭頭函數是一種更簡潔的匿名函數寫法,語法是fn(參數) => 表達式
,例如:
$y = 1;
// 普通匿名函數寫法
$fn1 = function($x) use ($y) {return $x + $y;
};
// 箭頭函數寫法
$fn2 = fn($x) => $x + $y;var_dump($fn1(2), $fn2(2)); // 打印結果都是3
可見,箭頭函數會自動引入外部變量$y
,無需使用use
關鍵字,更加方便。
但是,如果想要在函數內部修改外部變量的話,只能用匿名函數:
$y = 1;
// 普通匿名函數寫法
$fn1 = function() use (&$y) {$y++;
};
// 箭頭函數寫法
$fn2 = fn() => $y++;$fn2();
var_dump($y); // 輸出仍然是1,沒有變化$fn1();
var_dump($y); // 輸出2
箭頭函數的限制:
=>
后面只能跟一個表達式,不能包含多條語句- 不能修改父作用域變量
2)NULL合并賦值運算符:??=
NULL合并賦值運算符(Null coalescing assignment operator)(??=
)是對NULL合并運算符(??
)的一個升級:
$_GET['uid'] ??= 0; // 寫法比以下兩種寫法都要簡潔得多
var_dump($_GET['uid']); // 輸出:0// 等同于寫法1
$_GET['uid'] = isset($_GET['uid']) ? $_GET['uid'] : 0;// 等同于寫法2
$_GET['uid'] = $_GET['uid'] ?? 0;
3)類屬性支持類型聲明
類中的屬性支持聲明類型(callable
除外):
class User
{public int $uid;public string $name;public static int $age;
}
4)在數組中使用Spread運算符
Spread運算符(...
)用于將一個數組展開,現已支持在數組定義中使用:
$sub1 = ['a', 'b'];
$sub2 = ['c', 'd'];
$merge = [...$sub1, ...$sub2]; // 合并兩個數組
var_dump($merge);
僅支持展開索引數組,關聯數組需在PHP 8.1中才開始支持。
PHP 8.0
官方文檔
1)match表達式
match表達式用于對條件的值進行比較,然后進行分支計算,基本用法:
$status = 1;$ret = match($status) {0 => '禁用',1 => '啟用',default => '未知',
};var_dump($ret); // 輸出:啟用
match表達式跟switch表達式比較相似,它們之間的區別為:
1)match表達式在比較條件值的時候,用的是嚴格比較(===
),而switch用的是松散比較(==
):
$status = '1';$ret = match($status) {1 => '啟用1','1' => '啟用2',
};var_dump($ret); // 輸出:啟用2
2)match支持表達式計算:
$age = 15;$ret = match(true) {$age < 18 => '未成年',$age >= 18 => '已成年',
};var_dump($ret); // 輸出:未成年
甚至調用函數都可以:
$num = 4;$ret = match($num) {pow(2, 1) => '2的一次方',pow(2, 2) => '2的二次方',
};var_dump($ret); // 輸出:2的二次方
邏輯OR:
$num = 4;$ret = match($num) {2, 4, 6 => '偶數', // 邏輯OR,當$num為2或4或6時,分支條件成立
};var_dump($ret); // 輸出:偶數
3)match的分支必須列舉出所有可能的情況,如果所有分支條件都不成立,會拋出UnhandledMatchError
錯誤,我們可以使用default
關鍵字來解決此問題:
$status = 3;$ret = match($status) {0 => '禁用',1 => '啟用1',default => '未知', // 如果沒有此分支,會拋出錯誤
};var_dump($ret); // 輸出:未知
2)Nullsafe運算符
在對象的鏈式調用中,如果中間任一環節返回了NULL
,會導致PHP報錯:
$result = $repository->getUser(5)->name; // 如果getUser方法返回了NULL,就會報錯
可以使用NullSafe運算符?->
來解決此問題:
$result = $repository?->getUser(5)?->name; // 不會報錯,$result的值為NULL
3)命名參數
支持根據參數名傳參,好處:可以跳過某些默認值參數,不必再傳一遍。
function sum($a = 1, $b = 2, $c = 3)
{return $a + $b + $c;
}// 假設要傳c參數過去,其它參數保持默認值不變
// php8前,a、b參數是必須傳的,不能省略
sum(1, 2, 6);// php8,可以只傳c參數
sum(c: 6);// 甚至順序都可以打亂
sum(c: 6, a: 2, b: 3);
4)聯合類型聲明
使用|
來連接多個類型,它們之間是“或”的關系:
// $a的類型可以是int,或者float
function sum(int|float $a = 1, int|float $b = 2): float|int
{return $a + $b;
}
5)新增mixed類型
mixed
代表任何類型,可以接受任何類型的值,是頂級類型。
function foo(mixed $str): mixed
{
}
6)對象可以通過::class獲取類名
現在可以通過 $object::class
獲取類名,返回的結果和 get_class($object)
一致。
$obj = new StdClass();var_dump($obj::class); // 輸出:stdClass
var_dump(get_class($obj)); // 輸出:stdClass
7)構造器屬性提升
類的構造器里的參數,如果帶有可見性修飾符(private
/protected
/public
),那么這個參數會自動被添加為類的屬性:
class Person
{public function __construct(public string $name){}
}$obj = new Person('tim');
// 雖然沒有顯示定義name屬性,但仍然可以訪問
var_dump($obj->name); // 輸出:tim
8)注解
新增注解功能。
PHP 8.1
官方文檔
1)枚舉類型
枚舉類型Enum用于定義一組包含若干個可能值的集合,它本質上是一個類,可以像普通的class一樣,定義自己的方法、實現接口,但是不支持繼承、不支持new
實例化。
Enum分為純粹Enum(Pure Enum)和回退Enum(Backed Enum)兩種,定義:
// 純粹Enum,沒有case值
enum Pure
{case One;case Two;
}// 回退Enum,有case值
// case值僅支持string和int兩種類型
enum Backed: string
{case One = 'A';case Two = 'B';
}// 訪問單個case條目
var_dump(Pure::One);// 獲取Enum里的所有case條目
var_dump(Pure::cases()); // 返回的是一個數組
參數類型約束:
enum Status
{case Pending;case Completed;case Failed;
}function setStatus(Status $stat)
{
}setStatus(Status::Pending); // 合法值,正常
setStatus('success'); // success不是Status枚舉類型的合法值,報錯
Enum的每個條目(case)都是該Enum的一個對象,并且有一個只讀的name
屬性:
// Pending是Status的一個對象
var_dump(gettype(Status::Pending)); // 輸出:object
var_dump(Status::Pending instanceof Status); // 輸出:true// name屬性的值等于case條目本身的名稱
var_dump(Status::Pending->name); // 輸出:Pending
回退Enum的條目有個額外的只讀屬性value
, 它是定義時指定的值:
enum Backed: string
{case One = 'A';case Two = 'B';
}var_dump(Backed::One->value); // 輸出:A
根據case值轉換成Enum,可以使用from
或者tryFrom
方法:
enum Backed: string
{case One = 'A';case Two = 'B';
}$val1 = 'A';
var_dump(Backed::from($val1)); // 輸出:enum(Backed::One)$val2 = 'C';
var_dump(Backed::from($val2)); // C不是合法值,報ValueError錯誤
var_dump(Backed::tryFrom($val2)); // 不會報錯,輸出:NULL
將枚舉類型轉換為value=>文本
數組,可以用于HTML選擇框的展示:
enum UserStatus: int
{case DISABLED = 3;case NORMAL = 4;/*** 返回case條目對應的可讀中文描述* * @return string*/public function text(): string{return match($this) {self::DISABLED => '禁用',self::NORMAL => '啟用',};}
}$map = [];
foreach (UserStatus::cases() as $case) {$map[$case->value] = $case->text();
}
print_r($map); // 輸出:Array([3] => 禁用 [4] => 啟用)
2)Spread運算符支持展開關聯數組
在PHP 7.4版本已經支持在數組內使用Spread運算符(...
)展開數組,但當時僅支持索引數組的展開,現8.1版本已支持關聯數組:
$sub = ['c' => 'cat', 'd' => 'dog'];
$arr = ['a' => 'apple', ...$sub];
print_r($arr); // 輸出:['a' => 'apple', 'c' => 'cat', 'd' => 'dog']
注意:如果有相同的key,后面的值會覆蓋前面的值
3)新增never返回值類型
返回值聲明為never
的函數,代表它永遠都不會返回,因此不能包含return
語句。里面要么有調用exit()
結束腳本,要么拋出異常,要么永遠不會終止(例如死循環)。
function foo(): never
{exit(); // OKwhile(true) {} // OKthrow new Exception('Oh my god'); // OK
}
4)readonly屬性
可以使用 readonly 修飾符聲明類屬性,防止初始化后修改屬性。
class Person
{public readonly string $name;public function setName(string $name): void{$this->name = $name;}
}$p = new Person();
$p->setName('First name'); // 第一次賦值,正常
var_dump($p->name); // 輸出:First name
$p->setName('First name'); // 第二次賦值,報錯:Uncaught Error: Cannot modify readonly property Person::$name
注意:
readonly
不能用于修飾靜態屬性- 只讀屬性只能初始化一次,并且只能從聲明它的作用域內初始化。即使
clone
出來的新對象也不能重新初始化只讀屬性(自 PHP 8.3.0 起,可以重新初始化) - 只讀屬性不能設置默認值,因為具有默認值的只讀屬性等同于常量,因此不是特別有用
- 如果只讀屬性是一個對象或者資源,那么這個對象里的屬性仍然是可變的
class Name
{public string $firstName = 'first';
}class Person
{public readonly Name $nameObj;public function setName(Name $obj): void{$this->nameObj = $obj;}
}$p = new Person();
$p->setName(new Name()); // 第一次賦值,正常
var_dump($p->nameObj->firstName); // 輸出:first$p->nameObj->firstName = 'new'; // 修改只讀屬性對象的內部屬性,正常
var_dump($p->nameObj->firstName); // 輸出:new// 再次賦值,報錯
$p->setName(new Name());
5)交集類型聲明
使用&
來連接不同的類型,它們之間是“且”的關系:
// $val需滿足既是UnitEnum類型,又是ArrayAccess類型
function foo(UnitEnum&ArrayAccess $val)
{
}
6)可以使用final來修飾類常量
使用final
關鍵字修飾類常量,防止被子類修改:
class Foo
{final public const X = "foo";
}class Bar extends Foo
{public const X = "bar"; // 不能修改父類中的final常量
}// Fatal error: Bar::X cannot override final constant Foo::X
7)使用new初始化參數
參數、靜態變量、甚至全局常量都可以直接初始化為對象了:
define('MY_CONSTANT', new \StdClass());function foo(object $val = new \StdClass()): void
{
}
PHP 8.2
官方文檔
1)新增true/false/null類型
function foo(null $a, true $b, false $c)
{
}
過時:動態屬性
在舊版本中,如果嘗試在 object
上賦值不存在的屬性,PHP 將會自動創建相應的屬性:
class Person {}$obj = new Person();
$obj->name = 'xxx'; // 舊版本中,可以賦值不存在的屬性
var_dump($obj->name);
自 PHP 8.2.0 起此特性被標記為過時(Deprecated)。建議更改為屬性聲明。要處理任意屬性名稱,類應該實現魔術方法 __get()
和 __set()
。最后可以使用 #[\AllowDynamicProperties]
注解標記此類。