令人期待的PHP7.4
PHP 7.4 是下一個PHP 7 的次要版本,預計將於2019 年11 月28 日發佈到General Availability。讓我們來了解下PHP 7.4 新增的功能,這些新功能將使PHP 更快,更可靠。
當然,更令我期待是PHP 8。因為JIT的一些提議,已經獲得批准,這可能成為PHP的又一個里程碑。
PHP 7.4 有什麼新功能?
- 支持數組內解包- 數組擴展運算符
- 箭頭函數2.0 (更加簡短的閉包)
- NULL 合併運算符
- 弱引用
- 協變返回和逆變參數
- 預加載
數組表達式中引入Spread 運算符
自PHP 5.6起可用,參數解包是將數組和Traversable解包為參數列表的語法。要解壓一個數組或Traversable,必須以…(3點)為前綴,如下例所示:
function test(...$args) { var_dump($args); }
test(1, 2, 3);
然而PHP 7.4 RFC建議將此功能擴展到數組中去定義:
$arr = [...$args];
Spread運算符的第一個好處就是性能,RPC文檔指出 :
Spread運算符應該比
array_merge
擁有更好的性能。這不僅僅是Spread運算符是一個語法結構,而array_merge
是一個方法。還是在編譯時,優化了高效率的常量數組
Spread運算符的一個顯著優點是它支持任何可遍歷的對象,而該array_merge
函數僅支持數組。
以下是數組中參數帶有Spread 運算符的示例:
$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
var_dump($fruits);
如果在PHP 7.3 或更早版本中運行此代碼,PHP 會拋出一個Parse 錯誤:
Parse error: syntax error, unexpected '...' (T_ELLIPSIS), expecting ']' in /app/spread-operator.php on line 3
相反,PHP 7.4 將返回一個數組
array(5) {
[0]=>
string(6) "banana"
[1]=>
string(6) "orange"
[2]=>
string(5) "apple"
[3]=>
string(4) "pear"
[4]=>
string(10) "watermelon"
}
RFC 聲明我們可以多次擴展同一個數組。此外,我們可以在數組中的任何位置使用Spread Operator 語法,因為可以在spread 運算符之前或之後添加常規元素。因此,以下代碼將按預期工作:
$arr1 = [1, 2, 3];
$arr2 = [4, 5, 6];
$arr3 = [...$arr1, ...$arr2];
$arr4 = [...$arr1, ...$arr3, 7, 8, 9];
也可以將函數返回的數組作為參數,放到新數組中:
function buildArray(){
return ['red', 'green', 'blue'];
}
$arr1 = [...buildArray(), 'pink', 'violet', 'yellow'];
PHP 7.4 輸出以下數組:
array(6) {
[0]=>
string(3) "red"
[1]=>
string(5) "green"
[2]=>
string(4) "blue"
[3]=>
string(4) "pink"
[4]=>
string(6) "violet"
[5]=>
string(6) "yellow"
}
我們也可以使用生成器:
function generator() {
for ($i = 3; $i <= 5; $i++) {
yield $i;
}
}
$arr1 = [0, 1, 2, ...generator()];
但不允許通過引用傳遞的方式。請考慮以下示例:
$arr1 = ['red', 'green', 'blue'];
$arr2 = [...&$arr1];
如果我們嘗試通過傳遞引用的方式,PHP 會拋出以下Parse 錯誤:
Parse error: syntax error, unexpected '&' in /app/spread-operator.php on line 3
如果第一個數組的元素是通過引用存儲的,那麼它們也通過引用存儲在第二個數組中。這是一個例子:
$arr0 = 'red';
$arr1 = [&$arr0, 'green', 'blue'];
$arr2 = ['white', ...$arr1, 'black'];
這是我們用PHP 7.4 獲得的:
array(5) {
[0]=>
string(5) "white"
[1]=>
&string(3) "red"
[2]=>
string(5) "green"
[3]=>
string(4) "blue"
[4]=>
string(5) "black"
}
箭頭函數2.0 (簡短閉包)
在PHP中,匿名函數被認為是非常冗長且難以實現和難以維護的,RFC建議引入更簡單,更清晰的箭頭函數(或簡短閉包)語法,這樣我們就可以簡潔地編寫代碼。
在PHP 7.4 以前:
function cube($n){
return ($n * $n * $n);
}
$a = [1, 2, 3, 4, 5];
$b = array_map('cube', $a);
print_r($b);
PHP 7.4 允許使用更簡潔的語法,上面的函數可以重寫如下:
$a = [1, 2, 3, 4, 5];
$b = array_map(fn($n) => $n * $n * $n, $a);
print_r($b);
目前,由於語言結構,匿名函數(閉包)可以使用use
繼承父作用域中定義的變量,如下所示:
$factor = 10;
$calc = function($num) use($factor){
return $num * $factor;
};
但是在PHP 7.4 中,父級作用域的值是通過隱式捕獲的(隱式按值的作用域進行綁定)。所以我們可以用一行來完成一下這個函數:
$factor = 10;
$calc = fn($num) => $num * $factor;
父級作用域定義的變量可以用於箭頭函數,它跟我們使用use
是等價的,並且不可能被父級所修改。
新語法是對語言的一個很大改進,因為它允許我們構建更易讀和可維護的代碼。
NULL 合併運算符
由於日常使用中存在大量同時使用三元表達式和isset () 的情況, 我們添加了null 合併運算符(??) 這個語法糖。如果變量存在且值不為NULL, 它就會返回自身的值,否則返回它的第二個操作數。
$username = $_GET['user'] ?? ‘nobody';
這段代碼的作用非常簡單:它獲取請求參數並設置默認值(如果它不存在)。但是在RFC這個例子中,如果我們有更長的變量名稱呢?
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';
長遠來看,這段代碼可能難以維護。因此,旨在幫助開發人員編寫更直觀的代碼,這個RFC建議引入null合併等於運算符(null_coalesce_equal_operator) ??=
,所以我們可以敲下面這段代碼來替代上面的這段代碼:
$this->request->data['comments']['user_id'] ??= ‘value’;
如果左側參數的值為null
,則使用右側參數的值。
注意,雖然coalesce運算符??
是一個比較運算符,但??=
它是賦值運算符。
類型屬性2.0
類型的聲明,類型提示,以及指定確定類型的變量傳遞給函數或類的方法。其中類型提示是在PHP5的時候有的一個功能,PHP 7.2的時候添加了object
的數據類型。而PHP7.4更是增加了主類屬性聲明,看下面的例子:
class User {
public int $id;
public string $name;
}
除了void
和callable
外,所有的類型都支持:
public int $scalarType;
protected ClassName $classType;
private ?ClassName $nullableClassType;
為什麼不支持void
和callable
?下面是RFC的解釋
The
void
type is not supported, because it is not useful and has unclear semantics.
不支持void
類型,是因為它沒用,並且語義不清晰。The
callable
type is not supported, because its behavior is context dependent.
不支持callable
類型,因為其行為取決於上下文。
因此,我們可以放心使用bool
,int
,float
,string
,array
,object
,iterable
,self
,parent
,當然還有我們很少使用的nullable
空允許( ?type
)
所以你可以在PHP7.4 中這樣敲代碼:
// 静态属性的类型
public static iterable $staticProp;
// var 中声明属性
var bool $flagl
// 设置默认的值
// 注意,只有 nullable 的类型,才能设置默认值为 null
public string $str = "foo";
public ?string $nullableStr = null;
// 多个同类型变量的声明
public float $x, $y;
如果我們傳遞不符合給定類型的變量,會發生什麼?
class User {
public int $id;
public string $name;
}
$user = new User;
$user->id = 10;
$user->name = [];
// 这个会产生一个致命的错误
Fatal error: Uncaught TypeError: Typed property User::$name must be string, array used in /app/types.php:9
弱引用
在這個RFC中,提議引入WeakReference
這個類,弱引用允許編碼時保留對對象的引用,該引用不會阻止對像被破壞;這對於實現類似於緩存的結構非常有用。
該提案的作者Nikita Popov給出的一個例子:
$object = new stdClass;
$weakRef = WeakReference::create($object);
var_dump($weakRef->get());
unset($object);
var_dump($weakRef->get());
// 第一次 var_dump
object(stdClass)#1 (0) {}
// 第二次 var_dump,当 object 被销毁的时候,并不会抛出致命错误
NULL
協變返回和逆變參數
- Invariant (不變): 包好了所有需求類型
- Covariant (協變):類型從通用到具體
- Contravariant (逆變): 類型從具體到通用目前,PHP主要具有
Invariant
的參數類型,並且大多數是Invariant
的返回類型,這就意味著當我是T參數類型或者返回類型時,子類也必須是T的參數類型或者返回類型。但是往往會需要處理一些特殊情況,比如具體的返回類型,或者通用的輸入類型。而[RFC] 這個提案就提議,PHP7.4 添加協變返回和逆變參數,以下是提案給出來的例子:協變返回:interface Factory { function make(): object; } class UserFactory implements Factory { // 将比较泛的 object 类型,具体到 User 类型 function make(): User; }
逆變參數:interface Concatable { function concat(Iterator $input); } class Collection implements Concatable { // 将比较具体的 `Iterator`参数类型,逆变成接受所有的 `iterable`类型 function concat(iterable $input) {/* . . . */} }
預加載這個RFC是由Dmitry Stogov提出的,預加載是在模塊初始化的時候,將庫和框架加載到OPCache中的過程,如下圖所示引用他的原話:On server startup – before any application code is run – we may load a certain set of PHP files into memory – and make their contents “permanently available” to all subsequent requests that will be served by that server. All the functions and classes defined in these files will be available to requests out of the box, exactly like internal entities.服務器啟動時- 在運行任何應用程序代碼之前- 我們可以將一組PHP 文件加載到內存中- 並使得這些預加載的內容,在後續的所有請求中“永久可用”。這些文件中定義的所有函數和類在請求時,就可以開箱即用,與內置函數相同。預加載由php.ini
的opcache.preload
進行控制。這個參數指定在服務器啟動時編譯和執行的PHP腳本。此文件可用於預加載其他文件,或通過opcache_compile_file()
函數這在性能上有很大的提升,但是也有一個很明顯的缺點,RFC 提出來了preloaded files remain cached in opcache memory forever. Modification of their corresponding source files won’t have any effect without another server restart.預加載的文件會被永久緩存在opcache 內存中。在修改相應的源文件時,如果沒有重啟服務,修改就不會生效。