标题:【PHP小课堂】PHP中的函数相关处理方法学习

文章目录
    分类: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

    搜索
    关注