根據php RFC日程,PHP 7.4預期將會在今年11月份,當然接下來PHP 8被也已經上了歷程。7.4將至,PHP 8可期!那么即將到來PHP 7.4有啥新特性和功能呢?本文以蟲蟲以類型方面的增強來予以介紹,和大家一起學習。
概述
PHP 7.4為了增強類型新增加了類型化的類屬性,并對PHP的類型系統進行了重大改進。當然這些變化都是可選功能,是完全向前對老版本兼容,不會破壞以前的老代碼。新的類型特性:
從PHP 7.4開始支持。
僅在類中可用,并且需要訪問修飾符:public,protected或private或var。
允許使用除了void和callable以外的所有類型。
其表現如下例:
Uninitialized
在上面的例子中,首先映入我們眼簾的是:
class Foo { public int $bar; } $foo = new Foo;
即使創建Foo對象后$bar的值不是整數,PHP只會在訪問$bar時拋出錯誤:
var_dump($foo->a);
Fatal error: Uncaught Error: Typed property Foo::$bar
must not be accessed before initialization
從錯誤消息可以得出,有一種新的"可變狀態":uninitialized。
如果$bar沒有類型,則其值將為null。類型可以是空值,無法確定是否設置了類型化的可空屬性,或者忘記設置了。所以新添加了"uninitialized"這個新類型加以區別。
關于uninitialized,主要注意:
無法訪問未初始化的屬性,如果堅持要訪問會拋出致命錯誤。
在訪問屬性時會檢查未初始化狀態,所以即使其類型不可為空,也可以創建具有未初始化屬性的對象。
可以在讀取之前寫入未初始化的屬性。
對類型化屬性使用unset將使其未初始化,取消設置無類型屬性將使設置為null。
下面例子中在構造對象后設置未初始化的,不可為空的屬性,是有效的:
class Foo { public int $a; } $foo = new Foo; $foo->a = 1;
雖然只是讀取屬性值時檢查未初始化狀態,但在寫入時會進行類型驗證。所以可以確保不會被賦予錯誤類型的屬性值。
默認值和構造函數
再來看看如何初始化類型值。在標量類型的情況下,可以提供默認值:
class Foo { public int $bar = 4; public ?string $baz = null; public array $list = [1, 2, 3]; }
注意,如果類型實際上可以為空,則只能使用null作為默認值。可能看起來很明顯,但是參數默認值存在一些遺留行為,其中允許以下內容:
function passNull(int $i = null) { /* … */ } passNull(null);
好消息是,類型屬性不允許這種令人困惑的行為。
另請注意對對象或類類型的默認值是不可設置的。應該使用構造函數來設置其默認值。
初始化類型值的顯而易見的地方當然是構造函數:
class Foo { private int $a; public function __construct(int $a) { $this->a = $a; } }
在構造函數之外寫入未初始化的屬性是有效的。只要不去訪問這些屬性,就不會執行未初始化的檢查。
支持的類型
上面我們說了類型屬性只能在類中使用,并且需要訪問修飾符或前面的var關鍵字。從可用類型來看,除了void和callable之外,幾乎所有類型都可以使用。void類型表示沒有值,它不能用于鍵入值。然而,callable類型則更復雜點。
PHP中的"callable"類型用法是:
$callable = [$this, 'method'];
假設你代碼如下:
在上面的示例中,$callable引用私有的Bar::method方法,但是調用是在Foo的上下文中調用。由于作用域不同,所以callable也不能在類型屬性中使用。
這沒什么大不了的,因為Closure是一個有效的類型,它將記住構造它的$ this上下文。
除此之外,所有可用類型的列表如下:
布爾型(bool),整型(int),浮點型(float),字符串(string),數組(array),iterable,對象,?(nullable),self 和parent,類和接口
強制和嚴格的類型
PHP具有動態語言具有的類型靈活性,它會盡可能地強制或轉換類型。假設傳遞給整型變量一個字符串,PHP將嘗試自動轉換該字符串:
function coerce(int $i) { … } coerce('1'); // 1
類型屬性也使用同樣的原則。以下代碼有效,會自動將'1'轉換為1。
class Bar { public int $i; } $bar = new Bar; $bar->i = '1'; // 1
如果不喜歡這種行為,可以通過聲明為嚴格類型檢查來禁用:
declare(strict_types=1); $bar = new Bar; $bar->i = '1';
上述語句以錯誤類型賦值時候,在嚴格類型檢查(strict_types=1)下會拋出嚴重錯誤:
Fatal error: Uncaught TypeError:
Typed property Bar::$i must be int, string used
類型變量和繼承
盡管PHP 7.4引入了改進的類型變量,但類型屬性仍然是不變的。這意味著以下內容無效:
class A {} class B extends A {} class Foo { public A $prop; } class Bar extends Foo { public B $prop; }
Fatal error: Type of Bar::$prop must be A (as in class Foo)
如果上面的看起起不明顯,再舉個例子:
class Foo { public self $prop; } class Bar extends Foo { public self $prop; }
在運行代碼之前,PHP將使用它引用的具體類后臺替換self。所以上面的代碼也會拋出相同的錯誤。處理它的唯一正確姿勢是:
class Foo { public Foo $prop; } class Bar extends Foo { public Foo $prop; }
說到繼承,可能會發現很難找到任何好的用例來覆蓋繼承屬性的類型。
值得注意的是,可以更改繼承屬性的類型,但前提是訪問修飾符也從私有更改為protected或public。下面代碼是正確的:
class Foo { private int $prop; } class Bar extends Foo { public string $prop; }
不允許將類型從可空變為非可空,反之亦然。
class Foo { public int $a; public ?int $b; } class Bar extends Foo { public ?int $a; public int $b; }
Fatal error: Type of Bar::$a must be int (as in class Foo)
總結
本文我們以類型化屬性是PHP為例介紹了PHP 7.4帶來的新的變化和加強,更多的功能,可以參考PHP RFC。對于即將到來的7.4版本和后面可期的PHP 8 ,我們只有以活到老,學到老的態度才能跟上新技術的步伐。"求求你別更新了,我快跟不上啦!"絕非一個IT人應該有的態度!