PHP7标量类型声明RFC

一、总结

该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 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标量类型声明的介绍,因此,只翻译了一部分英文原文,并非全文完整翻译。敬请注意哈。

原文发布于微信公众号 - php(phpdaily)

原文发表时间:2015-09-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏老马说编程

计算机程序的思维逻辑 (第7节更新) - 再谈乱码恢复

在第6节和第7节,我们讨论了文本的二进制编码、乱码、以及恢复,第6节受到了很多读者的一致好评,但第7节有读者反馈解说的不太透彻,希望再详细一点,本文就是对第7节...

21460
来自专栏海说

深入理解计算机系统(3.3)---数据传送(或者说复制)指令详解

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer15.html

12840
来自专栏阿杜的世界

正则表达式生成—VerbalExpressions

从听说正则表达式这个词开始,我就没学会过怎么写正则表达式,也是有认真学过的,但是由于不经常用,学一次忘一次。前段时间遇到VerbalExpressions这个神...

10420
来自专栏编程

Go中defer的5 个坑-第一部分

首发于:https://studygolang.com/articles/12061 Go 中 defer 的 5 个坑 - 第一部分 通过本节的学习以避免掉入...

26650
来自专栏申龙斌的程序人生

零基础学编程008:print语句

在《零基础学编程007:FOR循环》这一篇文章里,我们只写了两行代码: for i in [1,2,3,4,5] : print( "(1+0.01)...

30470
来自专栏web前端教室

JavaScript ES6 模板字符串

偶然发现这个新东西,ES6也有模板了,是使用反引号`,来表示的。 这个新东西被称为字符串字面量,就是模板字符串。它使JS也有了简单的字符串插值特性。 为什么说是...

30890
来自专栏企鹅号快讯

PHP中被忽略的性能优化利器:生成器

如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP5.5.0才引入的功能,也...

399140
来自专栏公众号_薛勤的博客

深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

周志明的《深入理解Java虚拟机》很好很强大,阅读起来颇有点费劲,尤其是当你跟随作者的思路一直探究下去,开始会让你弄不清方向,难免有些你说的啥子的感觉。但知识不...

16820
来自专栏黄Java的地盘

【译】前端知识储备——Promise/A+规范

在面试别人的过程中,发现基本上没有人对整个Promise完全了解,因此希望通过这篇文章来帮助大家了解下Promise的全貌。本文的主要内容是Promise/A+...

16130
来自专栏鸿的学习笔记

Python写的Python解释器(一)

导论 Byterun是一个用Python实现的Python解释器。它的结构类似于CPython(Python的主流实现方式)。

18520

扫码关注云+社区

领取腾讯云代金券