谈谈 PHP 中的类型约束

起点

众所周知,PHP 是弱类型语言,与其他强类型语言项目,在这方面会有很多的坑,但是已经发展到 PHP 7 之后,PHP 也对类型约束有了所指,并且在许多流行框架中被大量使用比如Laravel,因为这确确实实在软件开发过程中无论是运行,还是 IDE 的代码提示都能为我们带来极大的便利,下面就一步步来看看 PHP 中的类型约束。

早期的约束

虽然 PHP 是隐式转换,但是在实际开发中也会存在一些无法转换的窘境,当然这些问题我们在开发阶段很容易发现,但是如果是一些动态的内容导致不可控就会呈现在用户面前,也就是 BUG ,在 PHP 中有一批以 is_* 开头的方法用来做一些简单类型判断(这其中一些方法也是新方法没有翻译的基本都是)。

  • is_array — 检测变量是否是数组
  • is_bool — 检测变量是否是布尔型
  • is_callable — 检测参数是否为合法的可调用结构
  • is_countable — Verify that the contents of a variable is a countable value
  • is_double — is_float 的别名
  • is_float — 检测变量是否是浮点型
  • is_int — 检测变量是否是整数
  • is_integer — is_int 的别名
  • is_iterable — Verify that the contents of a variable is an iterable value
  • is_long — is_int 的别名
  • is_null — 检测变量是否为 NULL
  • is_numeric — 检测变量是否为数字或数字字符串
  • is_object — 检测变量是否是一个对象
  • is_real — is_float 的别名
  • is_resource — 检测变量是否为资源类型
  • is_scalar — 检测变量是否是一个标量
  • is_string — 检测变量是否是字符串
  • is_a — 如果对象属于该类或该类是此对象的父类则返回 TRUE
  • is_subclass_of — 如果此对象是该类的子类,则返回 TRUE

在 PHP 5 之前,如果我们要做类型约束,那么就必须用到这些,这些方法对参数进行复杂的判断,并处理错误返回给调用者。

但是在 PHP 5 以来,在面向对象中,为方法带来了类型约束,然而这些都非常的鸡肋,从文档上可以看到。

  • PHP 5 支持 对象接口
  • PHP 5.1 支持 数组
  • PHP 5.4 支持匿名函数
  • 类型约束不能用于标量类型如 intstringTraits 也不允许。

在 PHP 5 中其实光是第一条,就够大部分场景使用,但是也有一些致命问题,比如最后一条的 不支持标量类型 ,也就是说支持不是很全面,而且还有一种情况没有考虑 那就 null 虽然 null 是一个特殊类型,但是有时候当数据不可控时也会出现,而且,在 PHP 5 阶段,类型约束并没有被很好的使用,或许是那个时候并不是那么的重视,毕竟弱类型是 PHP 的一大特点,但也是致命伤,甚至很多时候被强类型语言牵着鼻子走。

PHP 7

PHP 7 相对于先前的PHP版本可谓是焕然一新。

比较扎眼的就是完善了对类型限制的支持,补上了之前的短缺,包括标量类型返回值类型,而且,在 PHP 7.1 中还加入了严格类型验证

强制类型验证

strict_types/declare()指令

  • 默认情况下,所有的PHP文件都处于弱类型校验模式。新的declare指令,通过指定strict_types的值(1或者0),1表示严格类型校验模式,作用于函数调用和返回语句;0表示弱类型校验模式。
  • declare(strict_types=1)必须是文件的第一个语句。如果这个语句出现在文件的其他地方,将会产生一个编译错误,块模式是被明确禁止的。
  • 类似于encoding指令,但不同于ticks指令,strict_types指令只影响指定使用的文件,不会影响被它包含(通过include等方式)进来的其他文件。该指令在运行时编译,不能修改。它的运作方式,是在opcode中设置一个标志位,让函数调用和返回类型检查符合类型约束。

举个🌰

// 非严格模式
// 1️⃣
function testInt():int{
    return 0.01;
}
// 2️⃣
function testStr():string{
    return true;
}
// 3️⃣
function testBool():bool{
    return "1";
}
// 4️⃣
function testInt2():int{
    return "1string";
}

如你所见,上面的代码 通通都没有问题,都不会出现异常,甚至在部分 PHP 7.2 以下的版本中,4️⃣都是可以通过的。这是因为 PHP 7 虽然有了严格类型验证,但是默认情况下并没有启用,而是需要手动去启用,如果手动设置启用了之后,返回或者传递的参数不符合声明的类型,那么 PHP 会直接抛出一个 TypeError 错误,要求你去处理。启用强制类型验证 只需要在 PHP 文件的顶部加入以下代码即可。

declare(strict_types=1);

后话

类型验证不但有利于我们的程序在运行过程中所得到和返回的参数都是完全符合预期的并且还有另一个好处,那就是开发工具中的类型提示。

有时候可能会到一个情况,某个方法传递了一个参数为对象,里面有一些方法,但是 IDE 就是不提示。

interface UserInfo{
    getSex();
}
interface User{
    getUserInfo();
    getUserId();
    getUserName();
}
function getUserSex($user){
    // 你会发现 在这里 IDE 并不能很好的给你提示代码,和一些可以用的方法
    return $user->getUserInfo()->getSex();
}
class VipUser importants User(){
    // TODO .....
}
getUserSex(new VipUser());

这种情况下就 2 个解决方案了,如果你是项目,因为自 PHP 5 开始就支持对象的类型声明了,所以这里就不是那么担心,直接声明类型就好了。

function getUserSex(User $user){
    // 这里就可以提示了 
    return $user->getUserInfo()->getSex();
}

当然 还有方法就是使用 PHPDoc,即注解方案,这个方案已经在 PSR-5 中,虽然还没有完全通过,但是在 早期也有 PHPDoc 的一些 unofficial 的,而且主流 IDE 已经完全实现了,来协助我们提高开发体验。

最后

总结一下,PHP 中接近完善的 类型约束,让我们之前的一些不可能变成了可能,让一些不可靠变的更加的可靠,降低了代码中一些因为类型约束而导致的问题,从源头提升了在开发工具中的开发体验 。

参考资料

PHP7类型提示:作为PHP开发者应该永远铭记

阅读 2k

推荐阅读

记录收集一些开发中遇到的奇技淫巧和坑

38 人关注
12 篇文章
专栏主页