¿Para qué sirven las funciones anidadas de PHP?

80

En JavaScript las funciones anidadas son muy útiles: cierres, métodos privados y lo que tengas.

¿Para qué sirven las funciones PHP anidadas? ¿Alguien los usa y para qué?

Aquí hay una pequeña investigación que hice

<?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()
maullar
fuente
1
Podría haber jurado que leí que el soporte para esto estaba siendo eliminado en PHP6 pero no puedo encontrarlo en ningún lado.
Greg
2
@greg ¿Pensé que todo el plan para PHP6 estaba en el aire de todos modos?
James
Son excelentes para funciones grandes
especie de
También tiene cierres en PHP, no se preocupe.
Gralgrathor

Respuestas:

88

Básicamente, no hay ninguno. Siempre he tratado esto como un efecto secundario del analizador.

Eran Galperin se equivoca al pensar que estas funciones son de alguna manera privadas. Simplemente no se declaran hasta que outer()se ejecutan. Tampoco tienen un alcance privado; contaminan el ámbito mundial, aunque con retraso. Y como devolución de llamada, la devolución de llamada externa solo se puede llamar una vez. Todavía no veo cómo es útil aplicarlo en una matriz, que muy probablemente llama al alias más de una vez.

El único ejemplo del 'mundo real' que pude desenterrar es este , que solo puede ejecutarse una vez y podría reescribirse más limpio, en mi opinión.

El único uso que se me ocurre es que los módulos llamen a un [name]_includemétodo, que establece varios métodos anidados en el espacio global, combinados con

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

cheques.

OOP de PHP obviamente sería una mejor opción :)

Martijn Laarman
fuente
9
Si, en serio. Eso es brutalmente malo.
Tony Arkles
1
Gran enlace de ejemplo. ¡Debería comenzar a implementar eso en lugar de la herencia!
zanlok
igual que las defdeclaraciones en Ruby
user102008
1
A pesar de que no son exactamente funciones privadas, NO se pueden llamar A MENOS QUE se llame a la función externa, por lo que esto les da una especie de dependencia como una función que debe ejecutarse "junto" con la función externa ...
techexpert
2
Sin este comportamiento, la carga automática no funcionaría. Si las declaraciones dentro de una función son de alguna manera privadas, entonces el include / require realizado por su controlador de carga automática terminaría sin hacer nada.
Cleong
88

Si está utilizando PHP 5.3, puede obtener un comportamiento más similar a Javacript con una función anónima:

<?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
?>

Salida:

test
test
usuario641463
fuente
11
+1 por responder un tema (básicamente) funcional con una respuesta funcional, y no OOP
Peter Host
El autor de la pregunta debe actualizar la pregunta aceptada a esta. Esto es lo que realmente responde a la pregunta hasta mi época.
9

[Reescrito según el comentario de @PierredeLESPINAY.]

No es solo un efecto secundario, sino una característica muy útil para modificar dinámicamente la lógica de su programa. Es de los días de PHP procedimental, pero también puede ser útil con arquitecturas OO, si desea proporcionar implementaciones alternativas para ciertas funciones independientes de la manera más sencilla posible. (Si bien OO es la mejor opción la mayor parte del tiempo, es una opción, no un mandato, y algunas tareas simples no necesitan el esfuerzo adicional).

Por ejemplo, si carga complementos de forma dinámica / condicional desde su marco y desea facilitar la vida de los autores del complemento, puede proporcionar implementaciones predeterminadas para algunas funciones críticas que el complemento no anuló:

<?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... ;)
        }
    }
}
Sz.
fuente
2
Sin embargo, según el OP, el alcance de la función anidada parece no estar limitado a la función del contenedor ...
Pierre de LESPINAY
1
@PierredeLESPINAY: Vaya, muy cierto, ¡muchas gracias por señalarlo! : -o Actualicé (reescribí) la respuesta en consecuencia. (Es decir, sigue siendo una función muy útil, pero por una razón completamente diferente en ese momento).
Sz.
7

Funciones definidas dentro de funciones para las que no veo mucho uso, pero las funciones definidas condicionalmente puedo. Por ejemplo:

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

Y luego, todo lo que debe hacer su código es usar la función 'cmp' en cosas como llamadas a usort () para que no ensucie las verificaciones de idioma en todo su código. Ahora no he hecho esto, pero puedo ver argumentos para hacerlo.

cletus
fuente
1
En el pasado, hubiéramos llamado a este código de modificación automática. Una gran herramienta, pero tan peligrosa como GOTO para el abuso ...
Killroy
2
Mala idea. Mejor: use OO y no piratee los detalles del motor de secuencias de comandos.
zanlok
1
Tenga en cuenta que es posible desarmar funciones anónimas asignadas a variables.
BF
4

Dicho todo lo anterior, uno podría simplemente crear una función anidada para reemplazar algún código repetitivo localizado dentro de una función (que solo se usará dentro de la función principal). Una función anónima es un ejemplo perfecto de esto.

Algunos podrían decir simplemente crear métodos privados (o bloques de código más pequeños) en una clase, pero eso está enturbiando las aguas cuando una tarea ultraespecífica (que es exclusiva del padre) necesita ser modularizada, pero no necesariamente disponible para el resto de una clase. La buena noticia es que si resulta que necesita esa función en otro lugar, la solución es bastante elemental (mueva la definición a una ubicación más central).

En términos generales, usar JavaScript como estándar para evaluar otros lenguajes de programación basados ​​en C es una mala idea. JavaScript es definitivamente su propio animal en comparación con PHP, Python, Perl, C, C ++ y Java. Por supuesto, hay muchas similitudes generales, pero los detalles esenciales (referencia JavaScript: La Guía Definitiva, 6a Edición, Capítulos 1-12 ), cuando se les presta atención, hacen que el núcleo de JavaScript sea único, hermoso, diferente, simple y complejo todo al mismo tiempo. Esos son mis dos centavos.

Para que quede claro, no estoy diciendo que las funciones anidadas sean privadas. Solo ese anidamiento puede ayudar a evitar el desorden cuando algo trivial necesita ser modularizado (y solo lo necesita la función principal).

Anthony Rutledge
fuente
2

Todo mi php es OO, pero veo un uso para funciones anidadas, particularmente cuando su función es recursiva y no necesariamente un objeto. Es decir, no se llama fuera de la función en la que está anidado, sino que es recursivo y, posteriormente, debe ser una función.

No tiene mucho sentido crear un nuevo método para el uso expreso de un solo otro método. Para mí, ese es un código torpe y, en cierto modo, no es el punto de OO. Si nunca va a llamar a esa función en ningún otro lugar, anímela.

Jesse James Richard
fuente
1
Estás bastante en el dinero, pero creo que un mejor ejemplo sería al declarar funciones de devolución de llamada para array_filter (), array_map (), preg_replace_callback (), uasort () y similares. Utilizo estas funciones con bastante frecuencia, y rara vez necesito la devolución de llamada que estoy declarando fuera del método OOP desde el que lo estoy llamando, por lo que se siente mucho más limpio para evitar contaminar el espacio de nombres global o incluso de clase con la función de devolución de llamada . ¡Y finalmente puedo hacer eso con PHP 5.3 (como se explica en la respuesta del usuario614643)!
Derek
1

En las llamadas al servicio web, encontramos una sobrecarga mucho menor (memoria y velocidad) que incluye dinámicamente, de manera anidada, funciones individuales sobre bibliotecas llenas de miles de funciones. La pila de llamadas típica puede tener entre 5 y 10 llamadas de profundidad, solo es necesario vincular una docena de archivos de 1 a 2 kb dinámicamente, era mejor que incluir megabytes. Esto se hizo simplemente creando una pequeña función útil que requiere el envoltorio. Las funciones incluidas se anidan dentro de las funciones encima de la pila de llamadas. Considérelo en contraste con las clases llenas de cientos de funciones que no eran necesarias en cada llamada de servicio web, pero que también podrían haber utilizado las funciones de carga diferida incorporadas de php.

ZhuLien
fuente
1

si está en php 7, vea esto: Esta implementación le dará una idea clara sobre la función anidada. Supongamos que tenemos tres funciones (too (), boo () y zoo ()) anidadas en la función foo (). boo () y zoo () tienen la misma función anidada xoo (). Ahora en este código he comentado claramente las reglas de las funciones anidadas.

   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()

    }

Ahora, si llamamos a test1 (), la salida será esta:

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

si llamamos test2 () la salida será esta:

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

Pero no podemos llamar a text1 () y test2 () al mismo tiempo para evitar un error de re-declaración

princebillyGK
fuente
Esto sería más fácil de leer y digerir si los nombres de las funciones reflejaran alguna característica única de cada función, en lugar de nombres arbitrarios, que riman y parezcan similares. Es confuso y difícil de seguir. Elegir nombres que nos ayuden a realizar un seguimiento de la ubicación de cada uno lo haría fácil de usar y reduciría la carga cognitiva necesaria para leer y comprender. No tengo el tiempo, ni la voluntad de castigar, para superar esta publicación, aunque probablemente tengas un gran punto escondido allí, sospecho que pocos se quedarán para excavarlo. Reading SO no es un proyecto de investigación. KISS
SherylHohman
0

Sé que esta es una publicación antigua, pero primero uso funciones anidadas para dar un enfoque ordenado y ordenado a una llamada recursiva cuando solo necesito la funcionalidad localmente, por ejemplo, para construir objetos jerárquicos, etc. (obviamente, debe tener cuidado de que la función principal es solo llamado una vez):

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
}

Un punto a tener en cuenta en php en comparación con JS, por ejemplo, es que la llamada a la función anidada debe realizarse después, es decir, debajo de la declaración de la función (en comparación con JS, donde la llamada a la función puede estar en cualquier lugar dentro de la función principal


fuente
0

Realmente solo he usado esta característica cuando era útil para ejecutar una pequeña función recursiva dentro de una función primaria, más categórica, pero no quería moverla a un archivo diferente porque era fundamental para el comportamiento de un proceso primario. Me doy cuenta de que hay otras formas de "mejores prácticas" para hacer esto, pero quiero asegurarme de que mis desarrolladores vean esa función cada vez que miran mi analizador, es probable que sea lo que deberían modificar de todos modos ...

MJHd
fuente
-1

Las funciones anidadas son útiles en Memoization (almacenar en caché los resultados de la función para mejorar el rendimiento).

<?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);
}
?>
Pablo
fuente
3
Me gusta esta idea en principio, sin embargo, no creo que esto funcione en funciones que aceptan argumentos y usted está almacenando en caché en función de estos argumentos. Cuando la función se llama por segunda vez, con diferentes argumentos , el resultado no se habrá almacenado en caché e intentará volver a declarar, _foo()lo que resultará en un error fatal.
MrWhite
-1

Las funciones anidadas son útiles si desea que la función anidada utilice una variable que se declaró dentro de la función principal.

<?php
ParentFunc();
function ParentFunc()
{
  $var = 5;
  function NestedFunc()
  {
    global $var;
    $var = $var + 5;
    return $var;
  };
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
}
?>
Mateo
fuente
2
Así es como NUNCA debes hacerlo.
Denis V
1
@fiddler porque NestedFunc no está realmente anidado, se vuelve global.
Denis V