一、總結
此RFC建議新增4種新的標量型別宣告:int,float,string和bool,這些型別宣告將會和PHP原來的機制一致的用法。 RFC更推薦給每一個PHP文件,加入一句新的可選指令(declare(strict_type=1);),讓同一個PHP文件內的全部函數呼叫和語句返回,都有一個「嚴格約束」的標量類型聲明檢查。此外,在開啟嚴格型別約束後,呼叫拓展或PHP內建函數在參數解析失敗,將產生一個E_RECOVERABLE_ERROR級錯誤。透過這兩個特性,RFC希望編寫PHP能夠變得更準確和文件化。
#標量類型宣告:
#沒有新增的保留字
。 int、float、string和bool會被辨識為型別聲明,同時禁止用作class/interface/trait等的命名。新的用戶標量類型聲明,透過內部的Fast Parameter Parsing API實現。
strict_types/declare()指令
預設情況下,所有的PHP檔案都處於弱型別校驗模式。新的declare指令,透過指定strict_types的值(1或0),1表示嚴格型別校驗模式,作用於函數呼叫與傳回語句;0表示弱型別校驗模式。
declare(strict_types=1)必須是檔案的第一個語句。如果這個語句出現在檔案的其他地方,將會產生一個編譯錯誤,區塊模式是被明確禁止的。
類似於encoding指令,但不同於ticks指令,strict_types指令只影響指定使用的文件,不會影響被它包含(透過
include等方式)進來的其他檔案。該指令在運行時編譯,不能修改。它的運作方式,是在opcode中設定一個標誌位,讓函數呼叫和傳回類型檢查符合類型約束。
參數類型聲明
#該指令影響全部的函數調用,例如(嚴格校驗模式):
<?php declare(strict_types=1); foo(); // strictly type-checked functioncall function foobar() { foo(); // strictly type-checked function call } class baz { function foobar() { foo(); // strictly type-checked function call } }
對比(弱校驗模式)
<?php foo(); // weakly type-checked function call function foobar() { foo(); // weakly type-checked function call } class baz { function foobar() { foo(); // weakly type-checked function call } } ?>
傳回類型宣告:
#指令會影響同一個檔案下的所有函數的回傳類型. 例如(嚴格校驗模式):<?php
declare(strict_types=1);
function foobar(): int {
return 1.0; // strictly type-checked return
}
class baz {
function foobar(): int {
return 1.0; // strictly type-checked return
}}
?>
#<?php
function foobar(): int {
return 1.0; // weakly type-checked return
}
class baz {
function foobar(): int {
return 1.0; // weakly type-checked return
}}
?>
一个弱类型校验的函数调用,和PHP7之前的PHP版本是一致的(包括拓展和PHP内置函数)。通常,弱类型校验规则对于新的标量类型声明的处理是相同的,但是,唯一的例外是对NULL的处理。为了和我们现有类、调用、数组的类型声明保持一致,NULL不是默认的,除非它作为一个参数并且被显式赋值为NULL。
为了给不熟悉PHP现有的弱标量参数类型规则的读者,提供简短的总结。表格展示不同类型能够接受和转换的标量类型声明,NULL、arrays和resource不能接受标量类型声明,因此不在表格内。
*只有范围在PHP_INT_MIN和PHP_INT_MAX内的non-NaN float类型可以接受。(PHP7新增,可查看ZPP Failure on Overflow RFC)
?Non-numeric型字符串不被接受,Numeric型字符串跟随字符串的,也可以被接受,但是会产生一个notice。
?仅当它有toString方法时可以。
严格类型校验行为:
严格的类型校验调用拓展或者PHP内置函数,会改变zend_parse_parameters的行为。特别注意,失败的时候,它会产生E_RECOVERABLE_ERROR而不是E_WARNING。它遵循严格类型校验规则,而不是传统的弱类型校验规则。严格类型校验规则是非常直接的:只有当类型和指定类型声明匹配,它才会接受,否则拒绝。
有一个例外的是,宽泛类型转换是允许int变为float的,也就是说参数如果被声明为float类型,但是它仍然可以接受int参数。
<?php declare(strict_types=1); function add(float $a, float $b): float { return $a + $b;} add(1, 2); // float(3) ?>
在这种场景下,我们传递一个int参数给到定义接受float的函数,这个参数将会被转换为float。除此之外的转换,都是不被允许的。
三、例子:
让我们创建一个函数,让2个数相加。
add.php <?php function add(int $a, int $b): int { return $a + $b;} ?>
如果在分开的文件,我们可以调用add函数通过弱类型的方式
<?php require "add.php"; var_dump(add(1,2)); // int(3) // floats are truncated by default var_dump(add(1.5,2.5)); // int(3) //strings convert if there's a number part var_dump(add("1","2")); // int(3) ?>
默认情况下,弱类型声明允许使用转换,传递进去的值会被转换。
<?php require "add.php"; var_dump(add("1foo", "2")); // int(3) // Notice: A non well formed numeric value encountered
但是,通过可选择指令declare开启严格类型校验后,在这个场景下,相同的调用将会失败。
<?php declare(strict_types=1); require "add.php"; var_dump(add(1,2)); // int(3) var_dump(add(1.5,2.5)); // int(3) // Catchable fatal error: Argument 1 passed to add() must be of the type integer, float given
指令影响同一个文件下的所有函数调用,不管这个被调函数是否在这个文件内定义的,都会采用严格类型校验模式。
<?php declare(strict_types=1); $foo = substr(52,1); // Catchable fatal error: substr() expects parameter 1 to be string, integer given
标量类型声明也可以用于返回值的严格类型校验:
<?php function foobar(): int { return 1.0; } var_dump(foobar());// int(1)
在弱类型模式下,float被转为integer。
<?php declare(strict_types=1); function foobar(): int { return 1.0; } var_dump(foobar()); //Catchable fatal error: Return value of foobar() must be of the type integer,float returned
四、背景和理论基础
历史
PHP从PHP5.0开始已经有对支持class和interface参数类型声明,PHP5.1支持array以及PHP5.4支持callable。这些类型声明让PHP在执行的时候传入正确的参数,让函数签名具有更多的信息。
先前曾经想添加标量类型声明,例如Scalar Type Hints with Casts RFC,因为各种原因失败了:
(1)类型转换和校验机制,对于拓展和PHP内置函数不匹配。
(2)它遵循一个弱类型方法。
(3)它的“严格”弱类型修改尝试,既没有满足严格类型的粉丝期望,也没有满足弱类型的粉丝。
这个RFC尝试解决全部问题。
弱类型和强类型
在现代编程语言的实际应用中,有三种主要的方法去检查参数和返回值的类型:
(1)全严格类型检查(也就是不会有类型转换发生)。例如F#、GO、Haskell、Rust和Facebook的Hack的用法。
(2)广泛原始类型检查(“安全”的类型转换会发生)。例如Java、D和Pascal。他们允许广泛原始类型转换(隐式转换),也就是说,一个8-bit的integer可以根据函数参数需要,被隐形转换为一个16-bit的integer,而且int也可以被转换为float的浮点数。其他类型的隐式转换则不被允许。
(3)弱类型检查(允许所有类型转换,可能会引起警告),它被有限制地使用在C、C#、C++和Visual Basic中。它们尝试尽可能“不失败”,完成一次转换。
PHP在zend_parse_parameters的标量内部处理机制是采用了弱类型模式。PHP的对象处理机制采用了广泛类型检查方式,并不追求精确匹配和转换。
每个方法各有其优缺点。
这个提案中,默认采用弱类型校验机制,同时追加一个开关,允许转换为广泛类型校验机制(也就是严格类型校验机制)。
為什麼兩者都支援?
目前為止,大部分的標量類型宣告的擁護者都要求同時支援嚴格型別校驗和弱型別類型校驗,並非僅僅支持其中一種。這份RFC,使得弱型別校驗為預設行為,同時,加入一個可選的指令來使用嚴格型別校驗(同一個檔)。在這個選擇的背後,有許多個原因。
#PHP社群很大一部分人看起來很喜歡全#靜態類型。但是,新增嚴格型別校驗的標量類型宣告將會造成一些問題:
(1)造成明顯的不一致性:拓展和PHP內建函數對標量類型參數使用弱型別校驗,但是,使用者的PHP函數將會使用嚴格型別校驗。
(2)相當一部分人更喜歡弱型別校驗,並不認同這個提案,他們可能會阻止它的實施。
(3)已經存在的程式碼使用了PHP的弱型,它會受到影響。如果要求函數添加標量類型聲明到參數上,對於現有的程式碼庫,這將大大增加複雜性,特別是對於庫文件。
#這裡仍然有相當於一部分人是喜歡弱型別校驗的,但是,新增嚴格類型校驗宣告和新增弱型別校驗宣告都會引起一些問題:
(1)大部分傾向於嚴格類型校驗的人將不會喜歡這個提案,然後阻止它的實施。
(2)限制靜態解析的機會。 (可能是說,優化的機會)
(3)它會隱藏一些在類型自動轉換中資料遺失的bug。
第三種方案被提出來了,就是加入區分弱型別和嚴格型別宣告的語法。它也會帶來一些問題:
(1)不喜歡弱型別和嚴格型別校驗的人,會被強迫分別處理被定義為嚴格類型或弱類型校驗的函式庫。
(2)就像加入嚴格宣告一樣,這個也會和原來弱型別實作的拓展和PHP內建函數無法保持一致。
為了解決這三種方案帶來的問題,這個RFC提出了第四個方案:每個檔案各自定義嚴格或者弱型別校驗。它帶來了以下好處:
(1)人們可以選擇適合他們的類型校驗,也就是說,這個方案希望同時滿足嚴格和弱型校驗兩個陣營。
(2)API不會被強迫適應某個類型宣告模式。
(3)因為檔案預設使用弱型別校驗方案,已經存在的程式碼庫,可以在不破壞程式碼結構的情況下,新增標量類型聲明。也可以讓程式碼庫逐步新增類型聲明,或僅部分模組新增。
(4)只需要一個單一語法,就可以定義標量類型宣告。
(5)喜歡嚴格型別校驗的人,通常,不僅將這個特性使用在使用者定義的函數,同時也使用在拓展和PHP內建函數。也就是說,PHP使用者會得到一個統一機制,而不會產生嚴格標量聲明的矛盾。
(6)在嚴格類型校驗模式下,拓展和PHP內建函數產生的類型校驗失敗的錯誤級別,和使用者自訂函數產生的會保持一致,都是E_RECOVERABLE_ERROR。
(7)它允許嚴格類型和弱類型程式碼,在單一的程式碼庫中無縫整合。
o#
以上是詳解PHP7標量類型聲明RFC的範例程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!