自学内容网 自学内容网

【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


原文地址:https://blog.csdn.net/zhangyue0503/article/details/142391345

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!