1、匿名函数
从 PHP 5.3 开始,引入了对匿名函数的支持,所谓匿名函数就是在函数定义中没有显式声明函数名,在 PHP 中,匿名函数也被称作闭包函数(Closure)。
编写匿名函数
我们在 php_learning/function 目录下创建 closure.php 来存放本篇教程编写的代码。以上篇教程演示的自定义函数 add 为例,如果通过匿名函数进行定义,就是这样的:
上面第一个红色方框里面是匿名函数的定义部分,可以看到在 function 之后没有声明函数名,而是将整个函数赋值给了 $add 变量(不要漏掉赋值语句最后的分号),这样,$add 就变成了函数类型,也因此,函数在 PHP 中也可以看作是一等公民(first class),可以赋值给变量进行调用,此时,如果我们试图通过 var_dump($add) 打印 $add,结果如下:
可以看到它的类型是用于代表匿名函数的 Closure 类,并且该匿名函数支持两个必填参数 $a 和 $b。
回到 closure.php,在上述截图的第二个红色方框区域是匿名函数的调用部分,我们可以直接将 $add 作为一个函数名进行调用,打印结果是:
1 2=3
此外,还可以通过 PHP 内置的 call_user_func 函数调用该函数,第一个参数是函数名,后面的参数是函数参数(非匿名函数亦可通过 call_user_func 函数调用):
$sum=call_user_func($add,$a,$b);
返回结果和上面的 $add($a, $b) 完全一致。
可变数量的参数列表
如果感兴趣的话,看 call_user_func 函数的声明:
functioncall_user_func($function,…$parameter)
可以看到代表参数列表的 $parameter 前面有一个 … 前缀,其作用是标识该参数是一个可变数量的参数列表,也就是支持传入任意多个参数,从 0~N 个不等,比如我们这里传入的就是 $a 和 $b 两个参数,如果待调用函数 $function 不需要传递参数,则 $parameter 部分留空,如果只需要传入一个参数,则传入一个参数,依此类推。
默认参数
说到这里,我们还可以为函数设置默认参数,即为指定参数设置默认值,需要注意的是默认参数需要放到参数列表最后:
$add=function(int$a,int$b=2):int{return$a $b;};
这个时候,调用 $add 函数就可以不传入第二个参数了,该参数会使用默认参数值:
$n1=1;$n2=2;$sum=$add($n1);echo”$n1 $n2=$sum”.PHP_EOL;
当然,你可以可以传入第二个参数覆盖默认值:
$n1=1;$n2=3;$sum=$add($n1,$n2);echo”$n1 $n2=$sum”.PHP_EOL;
这样打印的结果就变成了:
1 3=4可变函数
最后,由于 $add 是一个函数类型变量,并且 PHP 是动态类型语言,所以我们还可以像操作基本类型变量那样将其他函数类型值赋值给 $add,这些函数类型值包括匿名函数和非匿名函数,比如我们新增一个两数相乘函数 multi,然后在运行时将其赋值给 $add:
注意第二个红色方框,我们在运行时将 multi 函数赋值给 $add,再调用 $add($n1, $n2) 则等同于调用 multi($n1, $n2),当然如果通过匿名函数定义 multi 也是可以的,对应的实现代码如下:
<?php/***通过匿名函数定义两数相加函数add*@paramint$a*@paramint$b*@returnint*/$add=function(int$a,int$b=2):int{return$a $b;};/***两数相乘函数multi*@paramint$a*@paramint$b*@returnint*/$multi=function(int$a,int$b):int{return$a*$b;};//调用匿名函数$n1=1;$n2=3;$sum=$add($n1,$n2);echo”$n1 $n2=$sum”.PHP_EOL;//将multi赋值给$add$add=$multi;$product=$add($n1,$n2);echo”$n1x$n2=$product”.PHP_EOL;
打印结果都是一样的:
这种在运行时动态设置函数类型值给变量的功能,在 PHP 中称之为可变函数。
2、作用域继承父作用域变量
匿名函数(或者叫闭包函数)的一个强大功能是支持在函数体中直接引用上下文变量(继承父作用域的变量),比如在上述代码中,我们可以这样编写匿名函数实现代码:
<?php$n1=1;$n2=3;//计算两数相加$add=function()use($n1,$n2){return$n1 $n2;};//计算两数相乘$multi=function()use($n1,$n2){return$n1*$n2;};//调用匿名函数$sum=$add();echo”$n1 $n2=$sum”.PHP_EOL;$product=$multi();echo”$n1x$n2=$product”.PHP_EOL;
只需要通过 use 关键字传递当前上下文中的变量,它们就可以在闭包函数体中直接使用,而不需要通过参数形式传入,这样一来,其他引用该文件的代码就可以间接引用当前父作用域下的变量,如果是在类方法中定义的匿名函数,则可以直接引用相应类实例的属性,关于这一块,学院君会在后续面向对象编程中详细介绍。
通过 global 声明全局变量
如果不是通过匿名函数的话,只能基于 global 关键字通过全局变量引用函数体外部定义的变量:
//计算两数相减functionsub(){global$n1,$n2;return$n1-$n2;}global vs. 匿名函数
从父作用域中继承变量与使用全局变量是不同的,全局变量存在于一个全局的范围,无论当前在执行的是哪个函数,而闭包的父作用域是定义该闭包的函数,不一定是调用它的函数。
我们编写一段示例代码来详细解释:
functionadd1($n1,$n2){returnfunction()use($n1,$n2){return$n1 $n2;};}functionadd2(){returnfunction(){global$n1,$n2,$n3;return$n1 $n2 $n3;};}$n1=1;$n2=3;$n3=4;$add=add1($n1,$n2);$sum=$add();echo”$n1 $n2=$sum”.PHP_EOL;$add=add2();$sum=$add();echo”$n1 $n2 $n3=$sum”.PHP_EOL;
在上述代码中,add1 中定义的闭包函数通过 use 引用了父作用域下的 $n1 和 $n2 变量,对于该闭包函数来说,其作用域是 add1 函数,而非调用它的位置,所以如果我们试图在 add1 中定义的闭包函数中通过 use 引用 $n3 会报错。
而 add2 中定义的闭包函数通过 global 关键字以全局变量的方式引用 $n1、$n2 和 $n3,全局变量存在于全局范围,与调用位置无关,所以可以成功引用。
上述代码的执行结果是:
global 的安全隐患
但实际编码中,尽量避免使用 global 关键字,因为一旦声明了全局变量,就可以在任何位置获取到这些全局变量,非常容易导致系统被攻击,比如我们新增一个函数 test,在这个函数内部就可以试图通过 $GLOBALS 获取对应全局变量:
functiontest(){printf(“n1=%d,n2=%d,n3=%d\n”,$GLOBALS[‘n1’],$GLOBALS[‘n2’],$GLOBALS[‘n3’]);}
匿名函数则有效规避了这种安全隐患。此外,匿名函数的另一个典型应用场景就是兜底处理(fallback),关于这一点,学院君将在作业项目中演示。
(全文完)
长按下面的二维码,即可订阅学院君最新发布的 Laravel 全栈工程师教程: