什么是PHP嵌套函数?


79

在JavaScript中,嵌套函数非常有用:闭包,私有方法以及您拥有的东西。

什么是嵌套PHP函数?有人使用它们吗?

这是我做的小调查

<?php
function outer( $msg ) {
    function inner( $msg ) {
        echo 'inner: '.$msg.' ';
    }
    echo 'outer: '.$msg.' ';
    inner( $msg );
}

inner( 'test1' );  // Fatal error:  Call to undefined function inner()
outer( 'test2' );  // outer: test2 inner: test2
inner( 'test3' );  // inner: test3
outer( 'test4' );  // Fatal error:  Cannot redeclare inner()

1
我可能发誓,我读到对PHP6的支持已被删除,但我在任何地方都找不到。
格雷格,

2
@greg我以为PHP6的整个计划还是悬而未决?
詹姆斯

它们非常适合大型功能
某种

您也已经用PHP进行了关闭,不费吹灰之力。
Gralgrathor

Answers:


87

基本上没有。我一直将其视为解析器的副作用。

Eran Galperin误认为这些功能是私有的。直到outer()运行它们才声明它们。它们也不是私有范围的;尽管确实拖延了,但它们确实污染了全球范围。作为回调,外部回调仍只能被调用一次。我仍然看不到将其应用于数组有什么帮助,数组很可能多次调用别名。

我可以挖掘的唯一“现实世界”示例是this,它只能运行一次,并且可以重写为更干净的IMO。

我能想到的唯一用途是让模块调用[name]_include方法,该方法在全局空间中设置多个嵌套方法,并与

if (!function_exists ('somefunc')) {
  function somefunc() { }
}

检查。

PHP的OOP显然是一个更好的选择:)


9
是的,真的。那太残酷了。
Tony Arkles 09年

1
很棒的例子链接。我应该开始实现它而不是继承!
zanlok 2011年

defRuby中的声明相同
user102008 2011年

1
即使它们不是完全私有的函数,除非调用外部函数,否则仍然不能调用它们,因此这使它们具有某种依赖关系,可以作为与外部函数“结合”运行的函数...
专家

2
没有这种行为,自动加载将无法进行。如果函数内的声明某种程度上是私有的,那么自动加载处理程序执行的include / require最终将什么都不做。
cleong 2012年

87

如果您使用的是PHP 5.3,则可以通过匿名函数获得更多类似于Javacript的行为:

<?php
function outer() {
    $inner=function() {
        echo "test\n";
    };

    $inner();
}

outer();
outer();

inner(); //PHP Fatal error:  Call to undefined function inner()
$inner(); //PHP Fatal error:  Function name must be a string
?>

输出:

test
test

11
+1用于实际回答(基本)功能性问题而不是OOP的问题-Peter
Host

问题作者应将此问题更新为已接受的问题。这是我回答问题之前真正回答的问题。

9

[根据@PierredeLESPINAY的评论重写。]

这不仅是副作用,而且实际上是动态修改程序逻辑的一个非常有用的功能。它来自过程式PHP时代,但如果您想以最直接的方式为某些独立功能提供替代实现,那么它也可以与OO体系结构一起使用。(虽然在大多数情况下,OO是更好的选择,但这是一个选择,而不是一项强制性要求,并且一些简单的任务不需要额外的精力。)

例如,如果您从框架中动态/有条件地加载插件,并希望使插件作者的工作变得非常轻松,则可以为插件未覆盖的一些关键功能提供默认实现:

<?php // Some framework module

function provide_defaults()
{
    // Make sure a critical function exists:
    if (!function_exists("tedious_plugin_callback"))
    {
        function tedious_plugin_callback()
        {
        // Complex code no plugin author ever bothers to customize... ;)
        }
    }
}

2
但是,根据OP,嵌套函数范围似乎不仅限于容器函数……
Pierre de LESPINAY,2016年

1
@PierredeLESPINAY:糟糕,非常正确,非常感谢您指出!:-o我相应地更新(重写)了答案。(即,它仍然是一个非常方便的功能,但当时的原因完全不同。)
Sz。

7

在函数中定义的函数我看不到太多用处,但我可以有条件地定义函数。例如:

if ($language == 'en') {
  function cmp($a, $b) { /* sort by English word order */ }
} else if ($language == 'de') {
  function cmp($a, $b) { /* sort by German word order; yes it's different */ }
} // etc

然后,所有代码所需要做的就是在usort()调用之类的事情中使用'cmp'函数,这样就不会在整个代码中乱码检查。现在,我还没有这样做,但是我可以看到执行此操作的参数。


1
过去,我们会调用此自修改代码。一个很棒的工具,但是像滥用GOTO一样危险……
Killroy

2
馊主意。更好:使用OO,而不要侵入脚本引擎的详细信息。
zanlok 2011年

1
请注意-可以取消设置变量分配的匿名函数。
2015年

4

综上所述,可以简单地创建一个嵌套函数来替换函数中的某些本地化重复代码(仅在父函数内部使用)。匿名函数就是一个很好的例子。

有人可能会说只是在一个类中创建私有方法(或较小的代码块),但这在需要对超特定任务(父级专有)进行模块化但又未必对其余任务可用时会感到困惑。一类。好消息是,如果事实证明您确实需要在其他地方使用该功能,则此修复很基本(将定义移至更中央的位置)。

一般来说,使用JavaScript作为评估其他基于C的编程语言的标准是一个坏主意。与PHP,Python,Perl,C,C ++和Java相比,JavaScript绝对是自己的动物。当然,有很多通用的相似之处,但是细节严谨(参考JavaScript:The Definitive Guide,第6版,第1-12章),当被注意时,可使核心JavaScript独特,美观,不同,简单而又实用。同时复杂。那是我的两分钱。

只是要清楚一点,我并不是说嵌套函数是私有的。当需要将一些琐碎的事情模块化时(仅父函数需要),嵌套可以帮助避免混乱。


2

我所有的php都是OO,但是我确实看到了嵌套函数的用法,特别是当您的函数是递归的并且不一定是对象时。也就是说,它不会在嵌套它的函数外部被调用,而是递归的,因此需要成为一个函数。

为快速使用其他单一方法而开发新方法毫无意义。对我来说,这是笨拙的代码,不是OO的重点。如果您永远不会在其他任何地方调用该函数,则将其嵌套。


1
您几乎可以省钱,但是我认为一个更好的例子是在声明array_filter(),array_map(),preg_replace_callback(),uasort()等回调函数时。我经常使用这些函数,并且很少需要在要从其调用的OOP方法之外声明的回调,因此避免使用回调函数污染全局乃至类命名空间感觉更加干净。最后,我可以使用PHP 5.3做到这一点(如user614643的答案所述)!
德里克(Derek)2013年

1

在网络服务调用中,我们发现它动态地降低了开销(内存和速度),从而以嵌套的方式动态地包含了满千个函数库中的各个函数。典型的调用堆栈可能在5-10次调用之间,仅要求动态链接十二个1-2kb文件比包含兆字节更好。只需创建一个小的util函数包装要求即可。包含的函数嵌套在调用堆栈上方的函数中。与每个Web服务调用都不需要的充满100多种功能的类相比,可以考虑一下它,但也可以使用php的内置延迟加载功能。


1

如果您使用的是php 7,则请参见以下内容: 此实现将为您提供有关嵌套函数的清晰思路。假设我们在函数foo()中嵌套了三个函数(too(),boo()和zoo())。boo()和zoo()具有相同的命名嵌套函数xoo()。现在,在这段代码中,我清楚地注释了嵌套函数的规则。

   function foo(){
        echo 'foo() is called'.'<br>';
        function too(){
            echo 'foo()->too() is called'.'<br>';
        }
        function boo(){
            echo 'foo()->boo() is called'.'<br>';
            function xoo(){
                echo 'foo()->boo()->xoo() is called'.'<br>';
            }
            function moo(){
                echo 'foo()->boo()->moo() is called'.'<br>';
            }
        }
        function zoo(){
            echo 'foo()->zoo() is called'.'<br>';
            function xoo(){     //same name as used in boo()->xoo();
                echo 'zoo()->xoo() is called'.'<br>';
            }
        #we can use same name for nested function more than once 
        #but we can not call more than one of the parent function
        }
    }

/****************************************************************
 * TO CALL A INNER FUNCTION YOU MUST CALL OUTER FUNCTIONS FIRST *
 ****************************************************************/
    #xoo();//error: as we have to declare foo() first as xoo() is nested in foo()

    function test1(){
        echo '<b>test1:</b><br>';
        foo(); //call foo()
        too();
        boo();
        too(); // we can can a function twice
        moo(); // moo() can be called as we have already called boo() and foo()
        xoo(); // xoo() can be called as we have already called boo() and foo()
        #zoo(); re-declaration error
        //we cannont call zoo() because we have already called boo() and both of them have same named nested function xoo()
    }

    function test2(){
        echo '<b>test2:</b><br>';
        foo(); //call foo()
        too();
        #moo(); 
        //we can not call moo() as the parent function boo() is not yet called
        zoo(); 
        xoo();
        #boo(); re-declaration error
        //we cannont call boo() because we have already called zoo() and both of them have same named nested function xoo()

    }

现在,如果我们调用test1(),输出将是这样的:

test1:
foo() is called
foo()->too() is called
foo()->boo() is called
foo()->too() is called
foo()->boo()->moo() is called
foo()->boo()->xoo() is called

如果我们调用test2(),输出将是这样的:

test2:
foo() is called
foo()->too() is called
foo()->zoo() is called
zoo()->xoo() is called

但是我们不能同时调用text1()和test2()以避免重新声明错误


如果函数名称反映了每个函数的某些独特特征,而不是任意的,押韵的,外观相似的名称,则将更易于阅读和理解。这令人困惑并且难以跟踪。选择有助于我们跟踪每个位置的名称可以使用户易于使用并减少阅读和理解所需的认知负担。我没有时间或惩罚的意愿来发表这篇文章,尽管您可能隐藏了一个要点,但我怀疑很少有人会坚持挖掘它。读SO不是研究项目。吻
SherylHohman

0

我知道这是一篇老文章,但是当我仅在本地需要功能时,例如,对于构建层次结构对象等(显然,您需要注意父函数仅是父函数),我使用嵌套函数为递归调用提供了一种整洁的方法叫过一次):

function main() {
    // Some code

    function addChildren ($parentVar) {
        // Do something
        if ($needsGrandChildren) addChildren ($childVar);
    }
    addChildren ($mainVar); // This call must be below nested func

    // Some more code
}

例如,与JS相比,php中需要注意的一点是,对嵌套函数的调用需要在函数声明之后(即下面)进行(与JS相比,在JS中,函数调用可以位于父函数中的任何位置)


0

当在主要的,更分类的函数中执行小的递归函数很有用,但又不想将其移至其他文件时,因为它对于主要流程的行为至关重要,因此我才真正使用了此特性。我意识到还有其他“最佳实践”方法可以做到这一点,但是我想确保我的开发人员每次查看我的解析器时都能看到该函数,很可能他们仍然应该修改它们。


-1

嵌套函数在记忆中很有用(缓存结果可提高性能)。

<?php
function foo($arg1, $arg2) {
    $cacheKey = "foo($arg1, $arg2)";
    if (! getCachedValue($cacheKey)) {
        function _foo($arg1, $arg2) {
            // whatever
            return $result;
        }
        $result = _foo($arg1, $arg2);
        setCachedValue($cacheKey, $result);
    }
    return getCachedValue($cacheKey);
}
?>

3
我原则上喜欢这个想法,但是,我认为这不适用于接受参数的函数,并且您基于这些args进行缓存。当函数第二次使用不同的args调用时,结果将不会被缓存,您将尝试重新声明_foo(),这将导致致命错误。
怀特先生(MrWhite)2011年

-1

如果希望嵌套函数使用在父函数中声明的变量,则嵌套函数很有用。

<?php
ParentFunc();
function ParentFunc()
{
  $var = 5;
  function NestedFunc()
  {
    global $var;
    $var = $var + 5;
    return $var;
  };
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
}
?>

2
这是你永远不应该做的。
丹尼斯五世

1
@fiddler,因为NestedFunc并不是真正的嵌套,所以它是全局的。
Denis V
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.