【PHP小课堂】PHP中的函数相关处理方法学习
PHP中的函数相关处理方法学习
在很早之前,面向过程的时代,函数方法是这些面向过程语言中的一等公民。在进步到面向对象之后,函数依然有着举足轻重的地位,它在类中成为了方法,但本质上,方法就是类的内部的一个函数。一般地,我们会将类外部定义的 function 称为函数,而将类的内部定义的 function 称为方法。我们的 PHP 也是从面向过程语言发展成为面向对象语言的一门编程语言,所以函数方法的支持也是非常全面的。今天我们学习的内容,是和 PHP 的函数操作有关的一些函数方法,它们为我们操作函数方法提供了许多方便有用的功能。
创建匿名函数
首先是创建一个匿名函数的方法,当然,之前的许多文章中我们也经常使用匿名函数来演示代码,不过今天介绍的这个与之前的匿名函数创建的方式略有不同。
$func = create_function('$a, $b', 'return $a+$b;');
echo $func(1,2), PHP_EOL; // 3
create_function() 方法就是用于创建一个函数,它的第一个参数是创建的函数的参数,第二个参数是函数体内部的内容。从代码中可以看出,这种形式创建函数非常地不直观,所以现在这个 create_function() 方法已经被标记为过时的,并在 PHP8 中不推荐使用了。更好地方式当然是直接地写我们的匿名函数就可以啦。
$func = function($a, $b){
return $a+$b;
};
echo $func(1, 2), PHP_EOL; // 3
函数参数操作
函数的参数操作也是非常常见的应用。
// 函数参数操作
function test($a, $b){
var_dump(func_get_args());
var_dump(func_num_args());
var_dump(func_get_arg(1));
}
test(1,2,3);
// array(3) {
// [0]=>
// int(1)
// [1]=>
// int(2)
// [2]=>
// int(3)
// }
// int(3)
// int(3)
func_get_args() 用于获取全部的参数,func_num_args() 用于获取参数的数量,func_get_arg() 用于获取指定位置的参数。PHP 和 Java 等静态语言的不同,除了变量类型之外,函数参数的不固定性也是其特点之一。这一点即有好处,也有坏处。比如说好处在于我们可以灵活地定义使用方法的参数,就像测试代码中的这个 test() 一样。我们在定义函数体的时候,只写了两个参数,但是我们是可以给它传递更多的参数的。多出来的参数就必须使用 func_get_arg() 来获取参数的值了,不能也没办法使用别的方式获取这个参数的内容。而坏处就是灵活过头了就会缺少契约的限制,从而导致在面向对象中的方法多态这一特性在 PHP 中无法简单的实现,因为方法多态的一个重要应用就是方法的重载,对于 Java 等语言来说,改变参数就可以实现方法的多种状态的重载,而 PHP 中是没有这种能力的。关于这方面的内容,我们在之前的文章 PHP中的“重载”是个啥?https://mp.weixin.qq.com/s/oYFp4PEqQu0Wx5Uo-Xnhew 中也讲过如何用 PHP 来实现方法重载多态的效果,大家可以参考一下。
判断函数是否存在
判断一个函数是否已经定义,或者说在当前的运行环境中是否存在,是很多业务和框架开发中非常常用的功能。
var_dump(function_exists('test')); // bool(true)
var_dump(function_exists('test1')); // bool(false)
var_dump(function_exists('str_replace')); // bool(true)
function_exists() 函数就是用于这个判断的,相信不用过多的解释了,和 class_exists() 这些都是类似的。不仅能够判断我们自定义的函数方法是否存在,也可以判断一些系统及扩展相关的函数是否存在。比如在很多 CMS 开源系统的初始化检测中,就一定会用到这类函数来检测当前运行环境是否符合系统的需求。
获得全部已定义的函数
通过一个函数,就可以获得整个系统环境中全部可以使用的方法名称,感觉怎么样,是不是很爽?
var_dump(get_defined_functions());
// array(2) {
// ["internal"]=>
// array(1544) {
// [0]=>
// string(12) "zend_version"
// [1]=>
// string(13) "func_num_args"
// [2]=>
// string(12) "func_get_arg"
// [3]=>
// string(13) "func_get_args"
// [4]=>
// string(6) "strlen"
// [5]=>
// string(6) "strcmp"
// …………………………
// …………………………
// …………………………
// …………………………
// [1540]=>
// string(15) "xmlwriter_flush"
// [1541]=>
// string(2) "dl"
// [1542]=>
// string(21) "cli_set_process_title"
// [1543]=>
// string(21) "cli_get_process_title"
// }
// ["user"]=>
// array(1) {
// [0]=>
// string(4) "test"
// }
// }
从输出的结果来看,系统或者扩展自带的函数,会放在 internal 下面,我们当前的系统环境中有 1543 个函数。当然,这个数量是根据我们不同的 PHP 版本以及安装的不同的扩展会有差别的。而我们自己定义的函数则会在 user 下面展示出来。
匿名回调方式调用函数
接下来就到了我们的重头戏部分,使用匿名回调的方式来调用一个函数。先看看怎么用,最后我们再说说具体的应用场景。
function call($a){
echo 'This is Test call(), arg is ', $a, PHP_EOL;
}
call_user_func('call', '1'); // This is Test call(), arg is 1
没错,如果上面的说明没看明白的话,看到这个 call_user_func() 函数或许不少人就明白了。这个函数也是很多面试中喜欢出现的一个函数,因为它其实是一个很神奇的函数。在测试代码中,我们可以看到,使用 call_user_func() 方法可以调用执行一个函数,感觉就和我们正常调用这个函数没什么区别呀?别急,我们最后再说这个问题。
继续来看看它的使用。
class A{
function ACall($a, $b){
echo 'This is class A Test call(), arg is ', $a, ' and ', $b, PHP_EOL;
}
}
call_user_func(['A', 'ACall'], '2', '22'); // This is class A Test call(), arg is 2 and 22
call_user_func(function($a){ echo 'This is anonymous Test call(), arg is ', $a, PHP_EOL;}, '3'); // This is anonymous Test call(), arg is 3
调用类中的方法,或者直接使用匿名函数都是可以的。如果是类中的方法的话,第一个参数需要是一个数组,这个数组中的第一个元素是类的名称,第二个元素是类中的方法名称。call_user_func() 中第二个以及后面的参数是可以有 N 多个的,它们代表的是传递给回调函数参数的参数值。
当然,我们还有更方便的一个函数可以传递参数值。
call_user_func_array('call', [1]); // This is Test call(), arg is 1
call_user_func_array(['A', 'ACall'], [2, 22]); // This is class A Test call(), arg is 2 and 22
或许更多的人会对这个 call_user_func_array() 更熟悉。而在面试中,也会有面试官问 call_user_func() 和 call_user_func_array() 的区别,这时就不要犯迷糊了,它们的区别就是对于参数的传递方式的不同,call_user_func_array() 的参数直接是以一个数组传递给回调函数的。
arg = 3;
call_user_func_array(function(&$a){ echo 'This is anonymous Test call(), arg is ', $a, PHP_EOL;$a++;}, [&$arg]); // This is anonymous Test call(), arg is 3
echo $arg; // 4
在回调函数的操作中,传递的参数值也可以是引用形式的,这一点其实和普通的函数调用是没有什么区别的。
调用静态函数
除了上面调用普通的函数以及方法外,PHP 中还提供了调用静态函数方法当做回调函数的功能。
class A2 {
const name = 'A2';
static function ACall(){
echo __CLASS__ , ' ', __METHOD__, ' ', static::name , ' ',join(func_get_args(), ','), PHP_EOL;
}
static function call(){
forward_static_call(['A2','ACall'], 1, 2);
forward_static_call('staticCall', 1, 2);
}
}
function staticCall(){
echo 'This is staticCall ', join(func_get_args(), ','), PHP_EOL;
}
A2::call();
// A2 A2::ACall A2 1,2
// This is staticCall 1,2
从功能上来看 forward_static_call() 和前面介绍的 call_user_func() 其实没什么太大的区别。而从使用角度看,forward_static_call() 必须是在类的方法中使用的,它可以调用类外部的函数。注意,这个 forward_static_call() 方法完全不能在类的外部使用的,直接就会报错。
既然说到了静态方法,那么静态方法的一些特别情况也是我们需要关注的,比如后期静态绑定的一些问题。
class A2Child extends A2{
const name = 'A2Child';
static function call(){
forward_static_call(['A2','ACall'], 1, 2);
call_user_func(['A2', 'ACall'], 1, 2);
}
}
A2Child::call();
// A2 A2::ACall A2Child 1,2
// A2 A2::ACall A2 1,2
幸好 forward_static_call() 对于后期静态绑定的支持是没有问题的。在上面的测试代码中,注意到我们输出的 static::name ,使用 forward_static_call() 和 call_user_func() 打印出来的结果是不同的哦。关于这方面的内容,可以参考之前文章 后期静态绑定在PHP中的使用https://mp.weixin.qq.com/s/N0rlafUCBFf3kZlRy5btYA 中的讲解。
同样地 forward_static_call() 也有一个 forward_static_call_array() 方法与之相对应,也是传递参数的方式不同。
class A3 extends A2{
const name = 'A3';
static function call(){
forward_static_call_array(['A2','ACall'], [1, 2]);
}
}
A3::call();
// A2 A2::ACall A3 1,2
讲完了函数的使用,接下来我们就好好讲一讲这些函数的真实作用。对于一个编程语言来说,过早地初始化暂时还不需要的内容是对资源的极大浪费。而在早期来说,我们在编译启动运行一个应用之后,所有的变量、方法、类对象都会加载到内容中,致使应用占用的空间非常庞大。而回调这种能力的出现,大大缓解的这个问题。为什么这么说呢?回调就像是事件一样,触发才执行,也就是说,我们可以在框架中定义好许多类、函数、方法,但是只有真正在执行到对应的功能时,才实例化并调用它们。这个可以参考 Laravel 中的服务容器以及路由的实现。大家可以在 vendor/laravel/framework/src/Illuminate/Routing/Controller.php 看到 callAction() 方法中正是使用了 call_user_func_array() 来实现路由中控制器的加载的。其实你在现在比较流行的任意一个框架的 vendor 目录中搜索 call_user_func 都可以看到非常多地使用这一系列功能函数的地方。
综上所述,call_user_func() 相关的这几个函数,在框架应用中非常广泛,也是我们向更高层次迈进所应该深入学习的内容。
在 PHP 中止时运行一个回调函数
最后,我们再来看一个非常简单的函数,也是我们在记录系统运行信息时非常常用的一个函数。
register_shutdown_function(function(){
echo join(func_get_args(), ',');
}, 1, 2);
// 1,2
register_shutdown_function() 函数就是注册一个在 PHP 结束运行时所执行的回调函数。不管是整个脚本执行完成还是调用了 exit 或者 die 结束运行之后,这个函数中定义的回调函数都会被执行。
总结
今天学习的内容最核心的地方就在于 call_user_func() 相关函数的应用,大家掌握好了吗?其它的比较有用的包括 function_exists() 和 func_get_arg() 这类的函数也是非常常见的,也是大家要好好了解掌握的内容。另外还有两个函数 register_tick_function() 以及 unregister_tick_function() ,这两个函数的使用场景非常少,也不多见,大家可以参考之前的文章 PHP没有定时器?https://mp.weixin.qq.com/s/NIYwhVLRl0drIcRvIoWvJA 中的相关讲解说明。
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/04/source/7.PHP%E4%B8%AD%E7%9A%84%E5%87%BD%E6%95%B0%E7%9B%B8%E5%85%B3%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95%E5%AD%A6%E4%B9%A0.php
参考文档:
https://www.php.net/manual/zh/book.funchand.php