PHP 7 入门:函数的增强

本文要点

  • 要在运行时创建命名的数组常量,我们可以使用新的 define()
    函数。

  • 要将对象作用域绑定到一个变量并调用它,我们可以使用新的 Closure::call()
    函数。

  • 要通过传统的 assert()
    使用表达式和/或自定义 AssertionError
    ,那么我们可以使用expectation。

  • PHP 7支持从Generator函数返回一个值。
  • PHP 7支持将一个Generator函数委托给另一个Generator函数。
  • 对于整数除法,使用名为 intdiv()
    的新函数。

  • 要覆盖 php.ini
    中的会话配置的设置,可以使用新的 session_start
    函数。

  • 要使用回调执行正则表达式搜索和替换,使用新的函数 preg_replace_callback_array()

  • PHP 7能够生成加密安全的整数和字节。
  • PHP 7.1支持将回调转换为闭包。
  • 箭头(=>)函数为匿名函数提供了简洁的语法。

PHP 7系列前面的文章中
,我们讨论了 PHP 类型系统的新特性。在本文中,我们将会探讨 PHP 7 在函数方面的改善。
PHP 支持多种类型的函数,包括用户自定义函数、内部函数、变量函数以及匿名函数。

定义数组常量的新函数

PHP 7.0 添加了名为 define()
的新函数,用来在运行时定义命名的数组常量。define()的语法如下所示:

bool define ( string $name , mixed $value [, bool $case_insensitive = FALSE ] )

表 1 讨论了该函数的参数

表 1 define()函数的参数

参数 描述
name 常量的名称。这个名称可以是保留字,但不建议这样做。
value 常量的值。这个值必须是一个标量值(整型、浮点、字符串、布尔值或NULL)或数组。
case_insensitive 常量是否区分大小写,默认是区分大小写的。在PHP 7.3.0中,不推荐采用不区分大小写的方式。

创建 PHP 脚本_constant.php_并定义名为 CONSTANT
的常量,如下所示。

define("CONSTANT", "Hello PHP");

我们还可以使用 const
关键字来定义常量:

const CONSTANT_2 = 'Hello php';const Constant = 'HELLO PHP';

复制代码

define()
函数可以用来定义数组常量:

define("Catalog", ['Oracle Magazine','Java Magazine']);

数组常量的值可以使用数组元素访问的方式进行输出。

echo Catalog[0]echo Catalog[1]

复制代码

_constant.php_文件如下所示:

<?phpdefine("CONSTANT", "Hello PHP");echo CONSTANT."
";
const CONSTANT_2 = 'Hello php';echo CONSTANT_2."
";
const Constant = 'HELLO PHP';echo Constant."
";
define("Catalog", ['Oracle Magazine','Java Magazine']);echo Catalog[0]."
";
echo Catalog[1]?>

复制代码

运行脚本将会输出脚本中定义的常量值。

Hello PHPHello phpHELLO PHPOracle MagazineJava Magazine

复制代码

全局定义的常量,如 TRUE
FALSE
,不能进行重定义。为了阐述这一点,我们创建一个_const.php_脚本,该脚本定义了常量 TRUE
,并将它的值设置成了 20

<?phpdefine('TRUE', 20);echo TRUE;?>

复制代码

如果你运行该脚本的话,它将会输出的值是 1
,这也是 TRUE
全局定义的值。

绑定对象作用域到闭包的新函数

闭包是用来表示匿名函数的类。PHP 7.0 引入了新的 Closure::call()
函数,能够非常简便地将对象作用域临时绑定到一个闭包并调用它。为了阐述这一点,创建脚本_closure.php_并复制如下清单所示的代码:

<?phpclass Hello {private function getMsg() {                  echo "Hello";            }}$getMsg = function() {return $this->getMsg();};echo $getMsg->call(new Hello);?>

复制代码

在上述脚本中,Hello 是一个具有函数 getMsg()
的类。 Closure::call()
函数用来创建一个 Hello
实例,并绑定它的作用域到一个调用 getMsg
方法的闭包。运行该脚本将会输出 Hello
消息。

Expectation

在讨论_expectation_之前,我们先看一下传统的断言。传统的 assert()
方法定义如下所示。它会检查如果代码的预期值(称为_断言_)是不是为 FALSE
,如果是 FALSE
的话,它会打印出描述消息,并且默认会中止程序。如果断言不是 FALSE
的话, assert()
没有任何效果。

bool assert ( mixed $assertion [, string $description ] )

按照设计,断言用于开发和测试期的调试,而不是运行期的操作。 assert()
的行为可以通过 assert_options()
或者 .ini
文件进行配置。

传统上, assert()
的第一个参数应该是一个要评估计算的字符串或者作为断言进行测试的 boolean 条件。如果所提供的是一个字符串的话,它会以 PHP 代码的形式进行评估计算。如果所提供的是一个 boolean 条件的话,那么在传递给 assert_options()
定义的断言回调函数(如果存在的话)之前,它将会被转换成字符串。

断言在 PHP 7 中进行了彻底修改,现在被称为_expectation_,并且 assert()
是 PHP 7 中的一个语言结构。作为对 assert()
的增强,expectations 被添加了进来,并且具有如下的语法:

bool assert ( mixed $assertion [, Throwable $exception ] )

有了 expectations 之后, assert()
的第一个参数可能是会返回一个值的表达式,而不再是一个要评估执行的 PHP 代码字符串或者要测试的 boolean 条件。表达式会被执行,得到的结果会被用来确定断言是否成功。从 PHP 7 开始,使用字符串作为第一个参数已经被废弃掉了。 assert_options()
依然能够与 expectations 协同使用,但是并不推荐这么做。相反,我们应该使用两个新的 php.ini 配置指令,如表 2 所示。

表 2 Expectations 的配置指令

配置指令 类型 描述 支持的值 默认值
zend.assertions integer 配置是否要生成断言代码并运行。 1:生成和运行断言代码(开发模式)** 0:生成断言代码但是并不会在运行时执行
**-1:不生成断言代码(生产模式)。使用该配置以便于使用expectations。
1
assert.exception 对于失败的断言抛出AssertionError或自定义异常 1:当断言失败的时候,要么抛出以异常的形式提供的对象,要么在没有提供异常的情况下抛出一个新的AssertionError对象。使用该配置以便于使用expectations。** **0:使用或生成上述的Throwable,但是只生成一个告警,而不抛出异常或AssertionError。 0

借助 expectations,第二个参数可能会是一个 Throwable
对象,而不再是字符串描述。为了在断言失败的时候抛出 Throwable
对象或异常, assert.exception
指令必须设置为 1。
相对于传统的断言,expectations 有如下的优势,不过为了向后兼容,传统的断言依然是支持的:

  • 可以用表达式来评估一个断言,而不是字符串或boolean值。
  • 通过使用 php.ini
    设置,断言代码即便已经生成,但可以在运行期跳过它们。甚至可能根本就不生成断言代码,对于生产环境的使用来说,推荐这样做。

  • 可以抛出自定义异常。PHP 7新增加了类 AssertionError

作为如何使用 expectations 的样例,我们创建一个名为_expectation.php_的脚本,并复制如下的代码清单到脚本中。这个脚本将这两个配置指令都设置成了 1。 assert
语言结构将第一个参数设置为 true,第二个参数设置为一个自定义的 AssertionError

<?php ini_set('assert.exception', 1);ini_set('zend.assertions', 1);assert(true, new AssertionError('Assertion failed.'));?>

复制代码

如果运行这个脚本的话,将不会抛出 AssertionError

接下来,将第一个参数设置为 false

assert(false, new AssertionError('Assertion failed.'));

如果再次运行脚本的话,expectation 将会失败并抛出 AssertonError

Uncaught AssertionError: Assertion failed

为了阐述在 assert()
中借助表达式和自定义 AssertionError
使用 expectations,我们首先从传统的使用 assert()
方式开始,测试一个变量是数字的断言。这里使用字符串来测试变量,并在断言失败的时候输出消息。

$numValue = '123string';assert('is_numeric_Value($numValue)' , "Assertion that $numValue is a number failed." );

复制代码

接下来,使用表达式作为 assert()
的第一个参数。然后,使用 AssertionError
对象来抛出自定义的错误信息。

$num = 10;assert($num > 50 , new AssertionError("Assertion that $num is greater than 50 failed.") );

复制代码

Generator 支持 return 表达式

Generator 函数是能够返回一个迭代对象的函数,比如 foreach
。Generator 函数是简单的迭代器,它们不需要类实现 Iterator
接口。Generator 函数的语法与普通函数的语法一样,只不过函数中包含一个或多个 yield
语句,当 Generator 函数所返回的迭代器被迭代的时候,每个 yield 语句都会生成一个值。Generator 函数必须要像正常的函数那样进行调用,并提供所有必要的参数。

PHP 7.0 为 Generator 函数添加了一个新的特性,那就是支持在所有的 yield 语句之后声明一个 return 语句。在 Generator 函数中所返回的值可以通过 Generator 函数返回对象的 getReturn()
函数来进行访问。我们不要将 Generator 函数返回的迭代器对象与 Generator 函数返回的值混淆。迭代器对象并不包含返回值,它只包含 yield 所生成的值。如果 Generator 函数需要执行某些计算并返回一个最终值的话,那么能够返回值就是非常有用的。Generators 可以声明 Generator
Iterator
Traversable
iterable
返回类型。

为了阐述如何使用这个新特性,我们创建名为 gen_return.php
的脚本,该脚本定义了带有多个 yield 语句的 Generator 函数并且在调用 Generator 函数的时候传入 1
作为参数。接下来,使用 foreach 迭代它的返回值并输出 yield 所生成的值。最后,使用 getReturn()
函数输出返回值。 gen_return.php
脚本如下所示:

<?php$gen_return = (function($var) {  $x=$var+2;  yield $var;  yield $x;  $y=$x+2;  return $y;})(1);foreach ($gen_return as $value) {  echo $value, PHP_EOL;}echo $gen_return->getReturn(), PHP_EOL;?>

复制代码

如果运行脚本的话,将会得到如下的输出:

1 3 5

Generator 委托

PHP 7.0 添加了对 Generator 委托的支持,这意味着一个 Generator 可以通过_yield from_关键字委托给另外一个 Generator、 Traversable
对象或数组。为了阐述 Generator 委托功能,我们创建名为 gen_yield_from.php
的脚本并定义两个 Generator 函数,即 gen($var)
gen2($var)
,其中 gen($var)
通过如下的语句委托给了 gen2($var)

yield from gen2($var);

随后,在一个 foreach
循环中遍历 gen($var)
所返回的迭代器对象。脚本 gen_yield_from.php
如下所示:

<?php function gen($var){  yield $var;  $x=$var+2;  yield $x;  yield from gen2($var);} function gen2($var){  $y=$var+1;  yield $var;  yield $y;}foreach (gen(1) as $val){  echo $val, PHP_EOL;} ?>

复制代码

运行脚本将会输出这两个 Generator 函数所生成的值:

1 3 1 2

整数除法的新函数

PHP 7.0 为整数除法添加了一个新函数 intdiv()
。这个函数会返回一个整数,该整数代表了两个整数的商,它具有如下的语法。

int intdiv ( int $dividend , int $divisor )

创建名为 int_div.php
的脚本以使用 intdiv()
函数,我们在这里添加一些整数除法的样例。像 PHP_INT_MAX
PHP_INT_MIN
这样的 PHP 常量可以用作该函数的参数。

<?phpvar_dump(intdiv(4, 2));var_dump(intdiv(5, 3));var_dump(intdiv(-4, 2));var_dump(intdiv(-7, 3));var_dump(intdiv(4, -2));var_dump(intdiv(5, -3));var_dump(intdiv(-4, -2));var_dump(intdiv(-5, -2));var_dump(intdiv(PHP_INT_MAX, PHP_INT_MAX));var_dump(intdiv(PHP_INT_MIN, PHP_INT_MIN));?>

复制代码

如果运行脚本的话,将会得到如下所示的输出:

int(2) int(1) int(-2) int(-2) int(-2) int(-1) int(2) int(2) int(1) int(1)

复制代码

如果存在 ArithmeticErrors
的话,它将会输出到浏览器中。为了阐述这一点,添加如下的函数调用并再次运行脚本。

var_dump(intdiv(PHP_INT_MIN, -1));

在本例中,将会生成一个 ArithmeticError
表明 PHP_INT_MIN
除以-1 的结果不是一个整数:

Uncaught ArithmeticError: Division of PHP_INT_MIN by -1 is not an integer

添加如下的函数调用到 int_div.php
脚本中并再次运行该脚本。

var_dump(intdiv(1, 0));

这次,将会抛出 DivisionByZeroError

Uncaught DivisionByZeroError: Division by zero

新的会话选项

session_start
函数可以用来开始一个新的会话(session)或恢复一个之前已经存在的会话。PHP 7.0 添加了对名为 options
的新参数的支持,该参数是一个相关选项的数组,它们可以覆盖 php.ini
中的会话配置指令。在 php.ini
中,这些会话配置指令均以 session.
开头,但是在以函数入参的形式为 session_start
提供 options 参数数组的时候, session.
前缀要省略。除了会话配置指令,还新增了一个 read_and_close
选项,如果该选项设置为 TRUE
,在读取之后,该会话会关闭,因为保持会话处于打开状态可能没有必要。作为样例,我们创建一个名为 session_start.php
的脚本并复制如下所示的代码清单。在样例脚本中, session_start(options)
函数调用通过数组设置了一些配置指令:

<?phpsession_start([  'name' => 'PHPSESSID',    'cache_limiter' => 'private',  'use_cookies' => '0']);

复制代码

在运行脚本的时候,脚本中的会话配置选项将会覆盖掉 php.ini
中所定义的会话配置指令(如果存在的话)。该脚本不会生成任何输出。

在 PHP 7.1 中,如果 session_start()
开启会话失败的话,它将会返回 FALSE
并且不会初始化 $_SESSION

使用回调执行正则表达式搜索和替换的新函数

PHP 7.0 增加了一个新的函数 preg_replace_callback_array()
,以便于使用回调进行正则表达式搜索和替换。这个函数与 preg_replace_callback()
函数类似,只不过回调是基于每个模式调用的。函数的语法如下所示:

mixed preg_replace_callback_array ( array $patterns_and_callbacks , mixed $subject [, int $limit = -1 [, int &$count ]] )

如果 $subject
参数是一个数组的话,它会返回一个字符串组成的数组,如果 $subject
是字符串的话,它会返回一个字符串。如果匹配上了的话,那么返回的数组和字符串就是新的主题(subject),如果找不到匹配项的话,那么将会返回未改变的主题。该函数的参数如表 3 所示。

表 3 preg_replace_callback_array 的函数参数

参数 类型 描述
$patterns_and_callbacks 数组 声明相关的数组,匹配模式(键)与回调(值)
$subject mixed 声明要搜索和替换的字符串或字符串数组
$limit int 指定每个subject字符串中每个模式的最大替换数的限制。默认是-1,也就是没有限制。
&$count int 替换完成的数量,存储在$count变量中。

为了阐述这个新的功能,创建样例脚本 prereg.php
并复制如下的代码清单到脚本中。主题或要搜索的样例字符串设置成了’AAaaaaa Bbbbb’。 patterns_and_callbacks
参数设置成了寻找匹配’A’和’b’的数量。

<?php$subject = 'AAaaaaa Bbbbb';preg_replace_callback_array(  [      '~[A]+~i' => function ($match) {          echo strlen($match[0]), ' matches for "A" found', PHP_EOL;      },      '~[b]+~i' => function ($match) {          echo strlen($match[0]), ' matches for "b" found', PHP_EOL;      }  ],  $subject);?>

复制代码

如果运行脚本的话,匹配’A’和’b’的数量将会被打印出来:

7 matches for "A" found 5 matches for "b" found

生成加密安全的整数和字节的新函数

新增了两个生成加密安全的整数和字节的新函数。这些函数在表 4 中进行了讨论。

表 4 新的加密函数

函数 语法 参数 返回值 描述
random_bytes() string random_bytes ( int $length ) int类型的$length,代表了以字节形式返回的任意字符串的长度。 返回一个字符串,其中包含了所请求数量的加密安全的随机字节。 生成和返回一个任意的字符串,包含了加密的随机字节。
random_int() int random_int ( int $min , int $max ) $min参数指定了返回值的下限,它必须等于或高于PHP_INT_MIN。$max参数指定了返回值的上限,它必须等于或低于PHP_INT_MAX。 加密的安全整数,这个数介于$min和$max之间。 生成和返回加密的任意整数。

作为样例,我们创建一个名为 random_int.php
的脚本,它会生成加密的任意整数。首先,生成一个范围在 1 和 99 之间的整数,然后生成一个范围在-100 到 0 的整数。

<?phpvar_dump(random_int(1, 99));var_dump(random_int(-100, 0));?>

复制代码

如果运行脚本的话,将会打印出两个整数。

int(98) int(-84)

生成的整数是随机的,如果相同的脚本再次运行的话,很可能会生成两个不同的整数。

接下来,创建另外一个样例脚本 random_bytes.php
,它会生成加密的任意字节,其长度为 10。然后,我们使用 bin2hex
函数将任意的字节转换成 ASCII 字符串,其中包含了所返回字节的 16 进制字符串形式。复制如下的代码清单到脚本文件中:

<?php$bytes = random_bytes(10);var_dump(bin2hex($bytes));?>

复制代码

运行脚本并生成加密的任意字节:

string(20) "ab9ad4234e7c6ceeb70d"

list()函数的修改

list()
函数用来为一个变量列表像数组那样进行赋值。PHP 7.0 和 7.1 为 list()带来了一些变更。在 PHP 7 中, list()
无法像之前的版本那样解包字符串,如果对字符串进行解包的话,将会返回 NULL

在 PHP 5.x 中,使用 list()
解包一个字符串的时候,会将 list()
中的一个变量赋值为字符串中的值。我们使用 PHP 5.x 中的 list()
来阐述解包字符串。创建脚本_list.php_并复制如下的代码清单到脚本中。

<?php$str = "aString";list($elem) = $str;var_dump($elem);

复制代码

该脚本解包 $str
中的一个值到一个列表元素中。使用 PHP 5.x 运行该脚本, $elem
的值会输出‘a’。如果你在 PHP 7.0 中再次运行该脚本的话,那么会输出 NULL
。另外一个无法在 PHP 7 中解包字符串的 list()
样例如下所示:

<?phplist($catalog) = "Oracle Magazine";var_dump($catalog);?>

复制代码

如果运行该脚本的话,与前面的脚本类似,我们会看到输出的值是 NULL

实际上,在 PHP 7.x 中,如果 list()
要通过字符串进行赋值,那么必须要使用 str_split
函数:

<?php$str = "aString";list($elem) = str_split($str);var_dump($elem);

复制代码

如果运行上述样例的话,列表元素按照预期被设置成了‘a’。在 PHP 7.0 中, list()
表达式不能完全为空。作为样例,运行如下 list.php
程序清单:

<?php$info = array('Oracle Magazine', 'January-February 2018', 'Oracle Publishing');list(, , ) = $info;echo " \n";

复制代码

这一次,会输出一条错误信息,表明“Cannot use empty list..”。列表中可以部分元素为空。在下面的代码清单中, list()
中的一个元素为空:

<?php$info = array('Oracle Magazine', 'January-February 2018', 'Oracle Publishing');list($name, $edition,) = $info;echo "$name  latest edition is $edition.\n";?>

复制代码

如果运行该脚本的话,不会生成错误,并且会创建一个具有两个非空元素和一个空元素的列表。

Oracle Magazine latest edition is January-February 2018.

list()
函数的另外一个修改就是按照变量定义的顺序为其进行赋值。在此之前,值是按照与定义相反的顺序进行赋值的。为了阐述之前的行为,使用 PHP 5.x 运行如下的 list.php
中的程序清单:

<?phplist($a[], $a[], $a[]) = ['A', 2, 3];var_dump($a);?>

复制代码

我们可以探测脚本的输出(参见图 1),值是按照定义相反的顺序进行赋值的。

图 1 值是按照相反的顺序赋值的

在 PHP 7.0 上运行相同的脚本,我们会发现_list()_会按照与定义相同的顺序为变量赋值:

array(3) { [0]=> string(1) "A" [1]=> int(2) [2]=> int(3) }

PHP 7.1.0 支持在列表中指定 key 以表明赋值的数字顺序。作为样例,创建 list.php
脚本并包含如下的代码清单。注意,在本例中,所有的 key 都是数字:

<?phplist(0 => $journal, 1=> $publisher, 2 => $edition) = ['Oracle Magazine', 'Oracle Publishing', 'January February 2018'];echo "$journal, $publisher, $edition. \n";?>

复制代码

如果运行脚本的话,将会得到如下的列表值:

Oracle Magazine, Oracle Publishing, January February 2018.

赋值的数字顺序可以交叉,如下面的代码清单所示,索引 0 放在了索引 1 的后面。

<?phplist(1 => $journal, 0=> $publisher, 2=>$edition) = ['Oracle Magazine', 'OraclePublishing', 'January February 2018'];echo "$journal, $publisher,$edition \n";?>

复制代码

如果运行脚本的话,输出的值表明变量是根据数字的 key 进行赋值的,而不是列表中 key 的顺序。

Oracle Publishing, Oracle Magazine,January February 2018

key 的索引可以使用单括号或双括号括起来,如下所示:

<?phplist('1' => $journal, '0'=> $publisher, '2'=>$edition) = ['Oracle Magazine', 'OraclePublishing', 'January February 2018'];echo "$journal, $publisher,$edition \n";?>

复制代码

上述脚本会生成与前面的脚本相同的输出。如果在 list()
中某个元素使用了 key,那么它的所有元素都应该使用 key。例如,创建一个列表,有些元素使用了 key 进行赋值,有些元素没有使用 key:

<?phplist(0 => $journal, 1=> $publisher, 2) = ['Oracle Magazine', 'Oracle Publishing', 'January February 2018'];echo "$journal, $publisher. \n";?>

复制代码

如果运行该脚本的话,我们会看到一个错误信息,提示在赋值的时候,带有 key 和不带 key 的数组条目不能混合使用:

Cannot mix keyed and unkeyed array entries in assignments

字符串偏移支持负数

从 PHP 7.1 开始,像 strpos
substr
这样的字符串操作函数引入了对负数偏移量的支持,也就是从字符串的结尾处开始处理偏移。使用[]和{}的字符串索引也支持负数偏移量。例如,”ABC”[-2]将会返回字母’B’。现在,我们创建一个脚本 str-negative-offset.php
并复制如下的代码清单到脚本中:

<?phpecho "ABCDEF"[-1];echo "
";
echo strpos("aabbcc", "a", -6);echo "
";
echo strpos("abcdef", "c", -1);echo "
";
echo strpos("abcdef", "c", -5);echo "
";
echo substr("ABCDEF", -1); echo "
";
echo substr("ABCDEF", -2, 5); echo "
";
echo substr("ABCDEF", -7);echo "
";
echo substr("ABCDEF", -6);echo "
";
echo substr("ABCDEF", -5);echo "
";
echo substr("ABCDEF", 6);echo "
";
echo substr("abcdef", 1, -3); echo "
";
echo substr("abcdef", 3, -2); echo "
";
echo substr("abcdef", 4, -1);echo "
";
echo substr("abcdef", -5, -2); ?>

复制代码

该脚本提供了多个在字符串函数和[]中使用负数偏移量的样例。如果运行脚本的话,它将会输出:

F0
2FEFABCDEFABCDEFBCDEF
bcdebcd

复制代码

将回调转换成闭包的新函数

闭包用来以字符串变量的形式传递函数(用户自定义的函数以及除语言构造之外的内置函数)和方法。例如,函数 hello()
可以以参数的形式传递给另外一个函数,或者使用函数名字以字符串的形式从另外一个函数中返回,如’hello’,当然这样做的前提是参数类型/返回类型为 callable。我们创建一个样例脚本 callable.php
并声明函数 hello()
,该函数输出一个‘hello’消息。声明另外函数,其参数类型是 callable。

function callFunc(callable $callback) {    $callback();}

复制代码

callFunc(callable)
函数能够以字符串的形式通过 hello()
的名字调用该函数:

callFunc("hello");

另外,内置的 call_user_func ( callable $callback [, mixed $... ] )
函数以 callable 作为其第一个参数,也可以用来根据名字调用 hello()
函数:

call_user_func('hello');

脚本_callable.php_如下所示:

<?phpfunction hello() {  echo 'hello';} call_user_func('hello');function callFunc(callable $callback) {  $callback();}echo '
';
callFunc("hello");

复制代码

如果运行脚本的话,会根据所提供的名称调用 hello()
函数。

hello

hello

闭包是匿名函数的对象表示形式。那为什么要将回调转换成闭包呢?有多个原因,其中一个就是性能。回调类型相对比较慢,因为确定一个函数是否为回调需要一定的成本。

使用回调的另外一个缺点在于,只有 public 的函数可以用作回调。相反,将类中的函数转换成闭包并不需要该函数是 public 的,例如该函数可以声明为 private。作为样例,我们创建一个脚本_hello.php_并声明一个类 Hello
,该类中包含返回一个回调函数的方法 getCallback()

public function getCallback() {        return [$this, 'hello_callback_function'];    }

复制代码

回调函数声明为 public。

public function hello_callback_function($name) { var_dump($name); }

创建该类的一个实例并调用回调函数。_hello.php_脚本如下所示:

<?phpclass Hello {  public function getCallback() {      return [$this, 'hello_callback_function'];  }  public function hello_callback_function($name) { var_dump($name);  }}$hello = new Hello();$callback = $hello-> getCallback();$callback('Deepak');

复制代码

如果运行脚本的话,会得到如下的输出:

string(6) "Deepak"

接下来,使用 Closure::fromCallable
静态方法将私有的回调函数转换成一个闭包。

<?phpclass Hello {  public function getClosure() {      return Closure::fromCallable([$this, 'hello_callback_function']);  }  private function hello_callback_function($name) { var_dump($name);  }}$hello = new Hello();$closure = $hello-> getClosure();$closure('Deepak');

复制代码

如果运行脚本的话,会得到相同的输出:

string(6) "Deepak"

转换成闭包的另外一个原因在于能够在早期探测到错误,不必推迟到运行期。考虑如上面所示的样例,但是这一次我们故意把函数名称拼错:

public function getCallback() {      return [$this, 'hello_callback_functio'];  }

复制代码

如果运行这个脚本的话,当回调函数在如下的语句实际执行的时候,将会抛出 Call to undefined method Hello::hello_callback_functio()
错误:

$callback('Deepak');

相反,如果我们将回调转换成闭包,错误 Failed to create closure from callable: class 'Hello' does not have a method 'hello_callback_function'
会在如下这行代码中就能探测出来:

return Closure::fromCallable([$this, 'hello_callback_functio']);

JSON_THROW_ON_ERROR 标记

在 PHP 7.3 版本之前,对 JSON 函数 json_encode()和 json_decode()的错误处理功能都是非常少的,有如下的不足之处:

  • 如果出现错误的话, json_decode()
    会返回null,但是null可能是一个合法的值,比如要对JSON “null”进行解码。判断是否有错误出现的唯一办法是使用 json_last_error()
    json_last_error_msg()
    查看全局的错误状态。 json_encode()
    没有错误的返回值。

  • 出现错误的时候,程序的运行不会停止,甚至不会抛出警告。

PHP 7.3 在 json_encode()
json_decode()
方法中添加了对 JSON_THROW_ON_ERROR
标记的支持。添加了新的异常子类 JsonException
,用来描述 JSON 解码/编码。如果为 json_encode()
json_decode()
提供 JSON_THROW_ON_ERROR
标记并抛出了 JsonException 异常的话,那么全局的错误状态不会被修改。为了阐述新的 JSON_THROW_ON_ERROR
JsonException
,我们创建一个_json.php_脚本,并尝试使用 json_decode
解码一个包含错误的数组:

<?phptry {$json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';    json_decode("{",false,1,JSON_THROW_ON_ERROR);}catch (\JsonException $exception) {  echo $exception->getMessage(); // echoes "Syntax error"}?>

复制代码

如果运行脚本的话,我们会看到如下所示的 JsonException

Maximum stack depth exceeded

作为使用 JSON_THROW_ON_ERROR
json_encode()
的样例,我们编码一个数组,该数组中包含一个值为 NAN 的元素,如下面的程序清单所示:

<?phptry {$arr = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => NAN);echo json_encode($arr,JSON_THROW_ON_ERROR);}catch (\JsonException $exception) {  echo $exception->getMessage(); }?>

复制代码

当运行脚本的时候,会输出如下的信息:

Inf and NaN cannot be JSON encoded

从数组中获取第一个和最后一个 key 值的新函数

从数组中获取第一个和最后一个 key 值是很常见的操作,PHP 7.3 专门新加了两个函数:

$key = array_key_first($array);$key = array_key_last($array);

复制代码

如下的代码清单给出了在相关数组甚至空数组中使用这两个函数的样例:

<?php// 在数组中的应用$array = ['a' => 'A', 2 => 'B', 'c' => 'C'];$firstKey =array_key_first($array);$lastKey = array_key_last($array);echo assert($firstKey === 'a');echo "
";
echo $firstKey;echo "
";
echo $lastKey;echo "
";
// 在空数组中的应用$array = [];$firstKey = array_key_first($array);$lastKey = array_key_last($array); echo "
";
echo assert($firstKey === null);echo "
";
echo assert($lastKey === null);?>

复制代码

脚本的输入如下所示:

1ac
11

复制代码

使用 Compact 函数报告未定义的变量

compact()
函数在 PHP 7.3 中有一个新的特性,那就是报告未定义的变量。为了阐述该功能,运行如下的脚本,该脚本中包含了一些未定义的变量:

<?php$array1=['a','b','c',];$var1="var 1";var_dump(compact($array1,$array2,'var1','var2'));?>

复制代码

脚本将会输出如下的信息:

Notice: Undefined variable: array2  on line 9Notice: compact(): Undefined variable: a  on line 9Notice: compact(): Undefined variable: b  on line 9Notice: compact(): Undefined variable: c   on line 9Notice: compact(): Undefined variable: var2  on line 9

复制代码

函数调用中的拖尾逗号

PHP 7.3 添加了在函数调用时使用拖尾逗号的支持。拖尾逗号在有些经常追加参数的场景中是很有用处的,比如可变参数的函数( array_merge
compact
sprintf
)。语言构造 unset()
isset()
也支持拖尾逗号。如下的样例使用 unset 函数阐述了拖尾逗号:

<?phpfunction funcA($A,$B,$C){    unset($A,$B,$C,);  echo $A;  echo $B;   echo $C;   }$A = 'variable A';$B = 'variable B';$C = 'variable C';funcA($A,$B,$C,);?>

复制代码

运行脚本,将会产生如下的输出:

Notice: Undefined variable: A Notice: Undefined variable: B Notice: Undefined variable: C 

复制代码

array_merge()
函数是另外一个可以借助拖尾逗号简化追加值的样例。如下的脚本使用在对 array_merge()
的函数调用中使用了拖尾逗号:

<?php$array1=[1,2,3];$array2=['A','B','C'];$array = array_merge(  $array1,  $array2,  ['4', '5'],);?>

复制代码

方法调用和闭包也允许使用拖尾逗号。在类中,方法就是一个函数。闭包是表示匿名函数的一个对象。拖尾逗号只能用于函数调用,不能用于函数声明。自由位置的逗号、前导逗号和多个拖尾逗号在该语言中是禁止使用的。

数学函数 bcscale

bcscale
函数的语法是 int bcscale ([ int $scale ])
,它能够为所有后续的 bc 数学函数调用设置默认的小数位数。像 bcadd()
bcdiv()
bcsqrt()
这样的 bc 数学函数能够用于任意精度的数字计算。PHP 7.3 添加了使用 bcscale 获取当前小数位数的支持。设置 bcscale 之后会返回旧的小数位数。作为样例,如下的脚本将默认的小数位数设置为 3,随后输出了当前的小数位数:

<?phpbcscale(3);echo bcscale();?>

复制代码

上述脚本的输出是 3。

新函数 is_countable

PHP 7.3 添加了新函数 is_countable
,如果函数参数为 array
类型或 Countable
实例的话,它会返回 true。

bool is_countable(mixed $var)

例如, is_countable()
能够用来判断给定的参数是不是数组。

echo is_countable(['A', 'B', 3]);

ArrayIterator
是可数的, is_countable
会输出 TRUE,因为 ArrayIterator
实现了 Countable 接口。

echo is_countable(new ArrayIterator());

is_countable
可以与 if()
一起使用,确保某个参数时可数的,然后再运行后续的代码。在如下的代码片段中,我们测试了类 A 的实例是不是可数的:

class A{}if (!is_countable(new A())) {  echo "Not countable";}

复制代码

上述代码片段的结果是 FALSE
,因为类 A 并没有实现 Countable
。如果 is_countable
的参数是一个数组的话,那么它将会返回 TRUE
,如下面的代码片段所示:

$array=['A', 'B', 3];if (is_countable($array)) {    var_dump(count($array)); }

复制代码

本节所有的代码片段均放到了 is_countable.php
脚本中。

<?phpclass A{}echo is_countable(['A', 'B', 3]);echo "
";
echo is_countable(new ArrayIterator());echo "
";
if (!is_countable(new A())) { echo "Not countable";}echo "
";
$array=['A', 'B', 3];if (is_countable($array)) { var_dump(count($array)); }?>

复制代码

运行该脚本,输出如下所示:

11Not countableint(3)

复制代码

箭头函数

PHP 7.4 引入了_箭头函数_,从而使匿名函数的语法更加简洁。箭头函数的形式如下所示:

fn(parameter_list) => expr

箭头函数具有最低的执行优先级,这意味着箭头 =>右边的表达式会在箭头函数之前执行,例如,箭头函数 fn($x) => $x + $y
等价于 fn($x) => ($x + $y)
,而不是 (fn($x) => $x) + $y
。在外围作用域中声明的且被表达式中使用的变量是隐式按值捕获的。作为样例,考虑如下的脚本,它声明了一个箭头函数:

$fn1 = fn($msg) => $msg.' '.$name;

变量 $name
会自动从封闭范围捕获,上述的箭头函数等价于:

$fn2 = function ($msg) use ($name) {    return $msg.' '.$name;};

复制代码

对 $x 的按值绑定等价于对箭头函数中每次出现 $x
均执行 use($x)
。箭头函数还声明了一个参数 $msg
。在下一个样例中, var_export
调用箭头函数并提供一个参数,输出值为’Hello John’:

<?php$name = "John";$fn1 = fn($msg) => $msg.' '.$name;var_export($fn1("Hello"));//'Hello John'?>

复制代码

箭头函数可以进行嵌套,如下面的脚本所示。外层的脚本函数按值捕获变量 $name
,内层的箭头函数从外层函数捕获 $name

<?php$name = "John";$fn = fn($msg1) => fn($msg2) => $msg1.' '.$msg2.' '.$name;var_export($fn("Hello")("Hi")); ?>

复制代码

当脚本运行的时候,输出入图 2 所示。

图 2 箭头函数可以进行嵌套

因为箭头函数使用按值的变量绑定,修改箭头函数中变量的值不会影响外层作用域中的值。为了阐述这一点,如下脚本中的箭头函数递减了外层代码块中变量



x











x 的值并没有影响,它依然是 1

<?php$x = 1$fn = fn() => x--; // 没有影响$fn();var_export($x);   //输出 1?>

复制代码

箭头函数支持任意的函数签名,可以包含参数和返回类型、默认值、可变参数以及按引用的变量传递和返回。如下的脚本阐述了箭头函数不同形式签名的使用。脚本中签名描述和输出通过注释 //
进行展示。

<?php$name = "John";$x = 1; //包括参数类型、返回类型和默认值的箭头函数 and default value$fn = fn(string $msg='Hi'): string => $msg.' '.$name;//包括可变参数的箭头函数  $fn2 = fn(...$x) => $x; //包括按引用参数传递的箭头函数$fn3=fn(&$x) => $x++;$fn3($x);echo $x; // 2var_export($fn("Hello"));//'Hello John'var_export($fn());//'Hi John'var_export($fn2(1,2,3)); //array ( 0 => 1, 1 => 2, 2 => 3, ) ?>

复制代码

箭头函数的对象上下文中可能会使用 $this
。如果与带有 static 前缀的箭头函数一起使用,那么就不能使用 $this
。为了阐述这一点,考虑如下的脚本,它在对象上下文和类上下文中使用了 $this
。如果不在对象上下文中使用的话,将会输出错误信息。

<?phpclass A {    public function fn1() {        $fn = fn() => var_dump($this);        $fn(); //  object(A)#1 (0) { }        $fn = static fn() => var_dump($this);        $fn(); //Uncaught Error: Using $this when not in object context        }}$a=new A();$a->fn1();

复制代码

总结

在该系列关于 PHP 7 新特性的第四篇(也是倒数第二篇)文章中,我们讨论了关于 PHP 函数的新特性。
在本系列的下一篇,也就是最后一篇中,我们将会讨论关于数组、操作符、常量和异常处理方面的新特性。

作者简介:

Deepak Vohra是一位 Sun 认证的 Java 程序员和 Sun 认证的 Web 组件开发人员。Deepak 在 WebLogic Developer’s Journal、XML Journal、ONJava、java.net、IBM developerWorks、Java Developer’s Journal、Oracle Magazine 和 devx 上都发表过 Java 和 Java EE 相关的技术文章。Deepak 还出版过五本关于 Docker 的书,他是 Docker 导师。Deepak 还发表了多篇关于 PHP 的文章,以及一本面向 PHP 和 Java 开发人员的 Ruby on Rails 图书。

原文链接:

Article: PHP 7 – Functions Improvements

相关阅读:

PHP 7 入门:新特性简介

PHP 7 入门:类和接口的增强

PHP 7 入门:数组、运算符、常量及异常处理的改进