不依赖PHP7和HHVM,如何在PHP中使用强类型

现在PHP7已经发布了一段时间了,当中有很多有趣的特性,比如:错误处理、合并空运算符、标量类型声明等等。并且你肯定也听说过,PHP是弱类型的语言,所以在开发当中有些事情变得无法预测。

尽管上面说的对的,但是PHP还是提供了一些方法让你自己的应用在你的掌控之中。现在让我们来看一下下面这段代码:

function plusone($a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));

// output

int(2)
int(2)
int(2)

这个函数的功能是将传入的参数加1然后输出。然而第二次和第三次执行的时候我们传入了一个字符串,并且函数输出了整型。这里发生的事情叫做字符串转换。通过验证我们可以保证用户传入的是一个数字类型的值。

function plusone($a)
{
    if ( !is_numeric($a) )
    {
        throw new InvalidArgumentException("I can only increment numbers!", 1);
    }

    return $a + 1;
}


在第三次调用函数的时候会抛出一个 InvalidArgumentException  错误信息。如果我们想指定传入参数的类型:
function plusone(int $a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));

// output

PHP Catchable fatal error:  Argument 1 passed to plusone() must be an instance of int, integer given, called in /vagrant/test_at/test.php on line 7 and defined in /vagrant/test_at/test.php on line 2

看到这个错误信息你可能会觉得有点奇怪,因为我们规定了传入的参数必须是个整型。
如果我们仔细看看这个错误信息,就会知道它说的是“必须是一个整型的实例”-这就说明了整型是一个类。

在PHP5中,函数的返回值会让人更加困惑。简单的说,我们无法自动锁定变量的类型,并且我们只有在函数执行后再检查返回的值是否和我们预期的一样。

增强类型

在PHP7之前的版本,Box开发组提出了一个想法来解决PHP5中由类型引发的安全问题。在使用了断言、类型提示等等方法之后,他们决定用cleaner来解决这一问题。

我们也看到了Facebook使用了HHVM和Hack来稍微推动了如何在PHP中解决这一问题。但是Box并不想改变PHP的源代码或者任何核心的东西。他们的解决方案是建立了一个独立的增强类型扩展,从而在PHP脚本执行的时候用phpDoc判断变量的类型。

安装

下面的扩展只适用于PHP5 - 如果你使用的是PHP7,你只需要坐在你的椅子上享受你的下午茶就好。

安装过程并不是很复杂,并且不需要任何的配置。下面介绍的方法基于Ubuntu,但是在其他Linux发行版和OS系统上也同样适用。
# update system
sudo apt-get update

# install required dependencies
sudo apt-get install php5-dev bison flex

# clone the repo
git clone git@github.com:box/augmented_types.git

# install extension

phpize
./configure --enable-augmented_types
make
make test
sudo make install

我们必须编辑php.ini文件来告诉PHP启用我们安装的扩展。
# Get php.ini location from PHP info. This will print the cli configuration file, you may need to edit /etc/php5/fpm/php.ini )
php -i | grep 'Loaded Configuration File'

# output: /etc/php5/cli/php.ini
vim /etc/php5/cli/php.ini

# Get `extension_dir` the PHP info
php -i | grep extension_dir
# output: extension_dir => /usr/lib/php5/20100525 => /usr/lib/php5/20100525

# Add this line at the end of the file
zend_extension=/usr/lib/php5/20100525/augmented_types.so


在每个文件中使用ini_set方法同样可以启用扩展。
ini_set("augmented_types.enforce_by_default",1);

如果你需要更多安装的细节,可以参考官方文档。 https://github.com/box/augmented_types/wiki/Installation

开始使用

之前我们提到了这个扩展是使用了phpDoc的原型。它的作用可以在下面的实例代码中加以解释。

/**
 * Add one
 *
 * @param   int $a
 * @return    int
 */
function plusone($a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));

你可以猜猜上面的代码会输出什么,但是你很可能会猜错。
int(2)
PHP Fatal error:  Wrong type encountered for argument 1 of function plusone, was expecting a integer but got a (string) '1' in /vagrant/test_at/test.php on line 15

第二次调用方法甚至都不会被执行!产生这个错误是因为PHP并没有像之前一样帮我们做类型转换。函数中严格要求传入一个整型类型。现在,如果我们传入一个浮点类型的值呢?
var_dump(plusone(1.5));
PHP Fatal error:  Wrong type encountered for argument 1 of function plusone, was expecting a integer but got a (float) 1.500000 in /vagrant/test_at/test.php on line 14

现在我们使用定义一个符合类型让我们的函数接受两种类型的值(整型和浮点型)。
/**
 * Add one
 *
 * @param   int|float $a
 * @return    int
 */
function plusone($a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone(1.5));

现在函数应该可以正常执行了。
int(2)
PHP Fatal error:  Wrong type returned by function plusone, was expecting a integer but got a (float) 2.500000 in /vagrant/test_at/test.php on line 0

出错了!其实我们同样应该设置函数的返回值类型。
/**
 * Add one
 *
 * @param   int|float $a
 * @return    int|float
 */
function plusone($a)
{
    return $a + 1;
}

我们同样可以使用符合类型:下面的例子使用的是数组的累加。

/**
 * Calculate sum
 *
 * @param   array $nums
 * @return    int
 */
function sum($nums)
{
    $sum = 0;
    foreach ($nums as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum([10, 12, 76]));
// output
int(98)

函数返回了期望的值。但如果数组中不只是包含整型类型的值呢?
var_dump(sum([10, 12, "something"]));

// output
int(22)

扩展使得我们可以使用数组类型。这个例子中,如果在数组中传入了其他的类型值,将会发生一个致命错误。

/**
 * Calculate sum
 *
 * @param   int[] $nums
 * @return    int
 */
function sum($nums)
{
    $sum = 0;
    foreach ($nums as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum([10, 12, 76]));
var_dump(sum([10, 12, "something"]));

int(98)
PHP Fatal error:  Wrong type encountered for argument 1 of function sum, was expecting a (integer)[] but got a array in /vagrant/test_at/test.php on line 20

如果我们想让这个函数接受任意类型的数字或者符合类型呢?这点我们也可以做到。

/**
 * Calculate sum
 *
 * @param   *int $nums
 * @return    int
 */
function sum($nums)
{
    $args = func_get_args();
    $sum = 0;
    foreach ($args as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum(10, 12, 76));

* int  的定义使得我们的函数接受任何数字类型的变量。我们可以使用 * int  和  int [] 让我们的函数做到前面说到的问题。

/**
 * Calculate sum
 *
 * @param   *int|int[] $nums
 * @return    int
 */
function sum($nums)
{
    if ( !is_array($nums) )
    {
        $nums = func_get_args();
    }

    $sum = 0;
    foreach ($nums as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum(10, 12, 76));
var_dump(sum([10, 12, 76]));


现在两次函数调用都会返回相同的值 ( int ( 98 ) ).

默认值

通常情况下,函数在接受参数之前都会做一些接受参数的初始化工作。我们可以使用void类型的定义来告诉扩展程序在这个变量不传入参数的时候也是可以的。

/**
 * SSH to server.
 *
 * @param   string      $url
 * @param   int|void    $port
 * @return  bool
 */
function ssh($url, $port = 2222)
{
    return true;
}


提示 : 当我们定义了默认参数并且传入参数可以为空的时候,扩展就不会强制判断类型。这就意味着当我们在port参数即使传入一个字符串类型的值也不会报错。

返回类型

之前我们也说到了,返回值的类型也应当被定义。每一个方法都应该有一个 @ return   < type > 定义,没有返回值的时候,应该定义 @ return  void
class User
{
    protected $id;

    protected $name;

    /**
     * Constructor
     * @param int       $id
     * @param string    $name
     * @return void
     */
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    /**
     * Return the user info as a string.
     *
     * @return    string
     */
    public function __toString()
    {
        return $this->name;
    }
}


忽略文件

通常情况下,我们的应用都会引入许多的类库,但是我们并不希望在这些类库引用的时候因为没有声明变量类型和返回类型而报错。但是在arguments_type扩展中,我们可以为文件和文件夹来设置相应的白名单和黑名单来解决这个问题。

augmented_types_blacklist ([ __DIR__ . "/vendor" ]);

现在扩展就会忽略我们的扩展目录了。如果在扩展目录下有我们自己需要监听类型的文件,我们还可以在这个方法之后使用白名单来再次启用扩展。参考官方文档来了解更多关于白名单和黑名单的方法: https://github.com/box/augmented_types/wiki/Whitelisting-and-Blacklisting

augmented_types_whitelist ([ __DIR__ . "/vendor/mylib" ]);

编辑php.ini文件同样可以达到上述效果,并且这是推荐做法。

# php.ini augmented_types.whitelist = ./vendoraugmented_types.blacklist = ./vendor/mylib

PHP7的变化

PHP7在方法和函数中使用了标量类型声明和返回类型声明,从而消除了对参数和返回类型的验证。你可以在PHP官网中看到这些特性。

如果PHP7已经支持了这些特性,那我们安装一个新的扩展有什么好处呢?

扩展提供了一些PHP7没有提供的选项 :
  • 复合类型 (@param string|int $var)
  • 数组类型 (@param int[] $var)
  • 制函数返回值(at least void)

总结

升级PHP7并且享受它的新特性是一个不错的主意。然而,我们并不是总能在我们的项目中使用这些最新的技术。

Argumented types扩展肯定会延长我们的开发时间(你肯定也料到了)这就是为什么这个扩展经常被使用在部署和测试环境中来保证我们的产品服务干净、高效。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值