写点什么

PHP 7 入门:函数的增强

2021 年 1 月 06 日

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 ] )</pre>


表 1 讨论了该函数的参数


<small>表 1 define()函数的参数</small>


参数描述
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."<br>";  const CONSTANT_2 = 'Hello php';echo CONSTANT_2."<br>";const Constant = 'HELLO PHP';echo Constant."<br>";define("Catalog", ['Oracle Magazine','Java Magazine']);echo Catalog[0]."<br>";echo Catalog[1]?>
复制代码


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


Hello PHPHello phpHELLO PHPOracle MagazineJava Magazine
复制代码


全局定义的常量,如TRUEFALSE,不能进行重定义。为了阐述这一点,我们创建一个_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 所示。


<small>表 2 Expectations 的配置指令</small>


配置指令类型描述支持的值默认值
zend.assertionsinteger配置是否要生成断言代码并运行。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 可以声明GeneratorIteratorTraversableiterable返回类型。


为了阐述如何使用这个新特性,我们创建名为 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_MAXPHP_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 所示。


<small>表 3 preg_replace_callback_array 的函数参数</small>


参数类型描述
$patterns_and_callbacks数组声明相关的数组,匹配模式(键)与回调(值)
$subjectmixed声明要搜索和替换的字符串或字符串数组
$limitint指定每个subject字符串中每个模式的最大替换数的限制。默认是-1,也就是没有限制。
&$countint替换完成的数量,存储在$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 中进行了讨论。


<small>表 4 新的加密函数</small>


函数语法参数返回值描述
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 开始,像strpossubstr这样的字符串操作函数引入了对负数偏移量的支持,也就是从字符串的结尾处开始处理偏移。使用[]和{}的字符串索引也支持负数偏移量。例如,"ABC"[-2]将会返回字母'B'。现在,我们创建一个脚本 str-negative-offset.php 并复制如下的代码清单到脚本中:


<?phpecho "ABCDEF"[-1];echo "<br/>";echo strpos("aabbcc", "a", -6);echo "<br/>";echo strpos("abcdef", "c", -1);echo "<br/>";echo strpos("abcdef", "c", -5);echo "<br/>";echo substr("ABCDEF", -1); echo "<br/>"; echo substr("ABCDEF", -2, 5);   echo "<br/>";echo substr("ABCDEF", -7);echo "<br/>";echo substr("ABCDEF", -6);echo "<br/>";echo substr("ABCDEF", -5);echo "<br/>";echo substr("ABCDEF", 6);echo "<br/>";echo substr("abcdef", 1, -3); echo "<br/>";echo substr("abcdef", 3, -2); echo "<br/>"; echo substr("abcdef", 4, -1);echo "<br/>";  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 '<br/>';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_ERRORJsonException,我们创建一个_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_ERRORjson_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 "<br/>"; echo $firstKey;echo "<br/>"; echo $lastKey;echo "<br/>";// 在空数组中的应用$array = [];$firstKey = array_key_first($array);$lastKey = array_key_last($array); echo "<br/>";echo assert($firstKey === null);echo "<br/>";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_mergecompactsprintf)。语言构造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<small>可以与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 "<br/>";  echo is_countable(new ArrayIterator());echo "<br/>"; if (!is_countable(new A())) {  echo "Not countable";}echo "<br/>";$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 的值并没有影响,它依然是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 入门:数组、运算符、常量及异常处理的改进


2021 年 1 月 06 日 15:051136

评论

发布
暂无评论
发现更多内容

架构师训练营第二期 Week 5 总结

bigxiang

极客大学架构师训练营

架构师训练营—第九周作业

Geek_shu1988

架构师训练营第九周课程笔记及心得

Airs

【架构师训练营】第九周作业:性能优化

MindController

秒杀系统

【喜讯】Apache DolphinScheduler 荣获 “2020 年度十大开源新锐项目”

海豚调度

Apache DolphinScheduler Apache DolphinScheduler 新一代大数据任务调度 十大开源新锐项目

一次用户故事拆(SPIDR)法实践

Bruce Talk

Agile 用户故事 User Story

架构师训练营第 9 周作业

netspecial

极客大学架构师训练营

InfoQ 写作平台的魔力

Yolanda

第9周作业1

Yangjing

极客大学架构师训练营

JVM垃圾回收原理,秒杀系统架构方案

garlic

极客大学架构师训练营

5G+工业互联网的中国登山队,如何攀跃“产业化”山峦?

脑极体

架构师训练营第 9 周课后练习

叶纪想

极客大学架构师训练营

架构师训练营 week5 课后作业

花果山

极客大学架构师训练营

架构师训练营第二期 Week 5 作业

bigxiang

极客大学架构师训练营

Week5 作业1

shuyaxx

真零基础Python开发web

MySQL从删库到跑路

Python django Web bottle

Week 9 作业01

Croesus

架构师训练营第 9 周学习总结

netspecial

极客大学架构师训练营

架构师训练营 - 第九周总结

一个节点

极客大学架构师训练营

【架构师训练营第 1 期 09 周】 学习总结

Bear

极客大学架构师训练营

技术选型总结一

Mars

技术选型

第9周作业2

Yangjing

极客大学架构师训练营

架构师训练营—第九周学习总结

Geek_shu1988

秒杀系统

橘子皮嚼着不脆

应届秋招生,熬夜吃透华为架构师这份‘典藏级’计算机网络+计算机操作系统,成功上岸腾讯

云流

网络协议 编程之路 计算机知识

架构师训练营 1 期第 9 周:性能优化(三)- 总结

piercebn

极客大学架构师训练营

Java 中常见的细粒度锁实现

rookiedev

Java 多线程 细粒度锁

能源区块链研究|区块链与核电安全

CECBC区块链专委会

区块链 核电

二分法求平方根,swift面向协议编程protocol从入门到精通、《格局》吴军著读后感、John 易筋 ARTS 打卡 Week 27

John(易筋)

collection ARTS 打卡计划 格局 吴军 李嘉图定律 面向协议protocol编程

架构师训练营 week5 学习总结

花果山

极客大学架构师训练营

助推城市智慧化!正舵者携手中科院演绎区块链魅力

CECBC区块链专委会

区块链 人工智能

PHP 7入门:函数的增强-InfoQ