該RFC建議新增4種新的標量型別宣告:int,float,string和bool,這些型別宣告將會和PHP原來的機制保持一致的用法。 RFC更推薦給每一個PHP文件,加入一句新的可選指令(declare(strict_type=1);),讓同一個PHP文件內的全部函數呼叫和語句返回,都有一個「嚴格約束」的標量類型聲明檢查。此外,在開啟嚴格型別約束後,呼叫拓展或PHP內建函數在參數解析失敗,將產生一個E_RECOVERABLE_ERROR級錯誤。透過這兩個特性,RFC希望編寫PHP能夠變得更準確和文件化。
推薦教學:《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 function call 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("1 foo", "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)它允許嚴格類型和弱類型程式碼,在單一的程式碼庫中無縫整合。
以上是PHP7標量類型聲明RFC詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!