在這篇文章中,我們將深入了解這個特性,但首先讓我們總結最重要的幾點:
● 它們自PHP 7.4起可用
● 它們只在類中可用,並且需要存取修飾符:public、protected或private;或var.
● 除了void和callable之外,所有型別都是允許的
他們的實際情況是這樣的:
class Foo { public int $a; public ?string $b = 'foo'; private Foo $prop; protected static string $static = 'default'; }
#未初始化
在檢視有趣的內容之前,首先要討論型別屬性的一個重要方面。
不管你第一眼看到的是什麼,下面的程式碼是有效的:
class Foo { public int $bar; } $foo = new Foo;
即使$bar的值不是一個整數後,使一個物件Foo, PHP只會丟擲一個錯誤時,$bar被存取:
var_dump($foo->bar); Fatal error: Uncaught Error: Typed property Foo::$bar must not be accessed before initialization
從錯誤訊息中可以看到,有一種新的「變數狀態」:未初始化。
如果$bar沒有型別,則其值將為null。但是型別可以為空,因此無法確定是否設定了型別為空的屬性,或者只是將其忘記了。這就是為什麼新增了「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」在PHP可以這樣寫:
$callable = [$this, 'method'];
假設您有以下(無效)程式碼:
class Foo { public callable $callable; public function __construct(callable $callable) { /* … */ } } class Bar { public Foo $foo; public function __construct() { $this->foo = new Foo([$this, 'method']) } private function method() { /* … */ } } $bar = new Bar; ($bar->foo->callable)();
在本例中,$callable參照私有Bar::方法,但是在Foo的上下文中被呼叫。由於這個問題,決定不新增callable的支援。
不過,這沒什麼大不了的,因為Closure是一個有效型別,它將記住構造它的$this上下文。
順便說一句,這是所有可用型別的列表:
● bool
● int
● float
● string
● array
● iterable
● object
● ? (nullable)
● self & parent
● Classes & interfaces
#強制型別和嚴格型別
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'; // 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; }
說到繼承,您可能會發現很難找到任何好的用例來覆蓋繼承屬性的型別。
雖然我同意這種觀點,但值得注意的是,可以更改繼承屬性的型別,但前提是存取修飾符也從private更改為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)
翻譯:https://stitcher.io/blog/typed-properties-in-php-74
以上就是PHP 7.4中的型別屬性(Typed Properties)的詳細內容,更多請關注TW511.COM其它相關文章!