Wofür sind PHP-verschachtelte Funktionen?

79

In JavaScript sind verschachtelte Funktionen sehr nützlich: Abschlüsse, private Methoden und was haben Sie ..

Wofür sind verschachtelte PHP-Funktionen? Verwendet jemand sie und wofür?

Hier ist eine kleine Untersuchung, die ich durchgeführt habe

<?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()
meouw
quelle
1
Ich hätte schwören können, dass ich gelesen habe, dass die Unterstützung dafür in PHP6 eingestellt wurde, aber ich kann sie nirgendwo finden.
Greg
2
@greg Ich dachte, der ganze Plan für PHP6 liegt sowieso in der Luft?
James
Sie
eignen sich
Sie haben auch Schließungen in PHP, kein Schweiß.
Gralgrathor

Antworten:

87

Grundsätzlich gibt es keine. Ich habe dies immer als Nebeneffekt des Parsers behandelt.

Eran Galperin glaubt fälschlicherweise, dass diese Funktionen irgendwie privat sind. Sie werden einfach nicht deklariert, bis sie outer()ausgeführt werden. Sie sind auch nicht privat begrenzt; Sie verschmutzen den globalen Geltungsbereich, wenn auch verzögert. Und als Rückruf konnte der äußere Rückruf immer noch nur einmal aufgerufen werden. Ich sehe immer noch nicht, wie hilfreich es ist, es auf ein Array anzuwenden, das den Alias ​​sehr wahrscheinlich mehrmals aufruft.

Das einzige Beispiel aus der 'realen Welt', das ich ausgraben könnte, ist dieses , das nur einmal ausgeführt werden kann und sauberer umgeschrieben werden könnte, IMO.

Die einzige Verwendung, die ich mir vorstellen kann, besteht darin, dass Module eine [name]_includeMethode aufrufen , die mehrere verschachtelte Methoden im globalen Raum in Kombination mit festlegt

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

prüft.

PHPs OOP wäre natürlich die bessere Wahl :)

Martijn Laarman
quelle
9
Ja wirklich. Das ist brutal schlecht.
Tony Arkles
1
Toller Beispiellink. Ich sollte anfangen, das anstelle der Vererbung zu implementieren!
Zanlok
wie defDeklarationen in Ruby
user102008
1
Auch wenn es sich nicht gerade um private Funktionen handelt, können sie NICHT aufgerufen werden, es sei denn, die äußere Funktion wird aufgerufen. Dies gibt ihnen eine Art Abhängigkeit als Funktion, die "in Verbindung" mit der äußeren Funktion ausgeführt werden soll ...
techexpert
2
Ohne dieses Verhalten würde das automatische Laden nicht funktionieren. Wenn Deklarationen innerhalb einer Funktion irgendwie privat sind, würde das von Ihrem Autoload-Handler ausgeführte Include / Require am Ende nichts bewirken.
Cleong
87

Wenn Sie PHP 5.3 verwenden, können Sie mit einer anonymen Funktion mehr Javacript-ähnliches Verhalten erzielen:

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

Ausgabe:

test
test
user641463
quelle
11
+1 für die tatsächliche Beantwortung eines (im Grunde) funktionalen Themas mit einer funktionalen Antwort und nicht OOP
Peter Host
Der Autor der Frage sollte die akzeptierte Frage auf diese aktualisieren. Dies ist, was die Frage bis zu meiner Zeit tatsächlich beantwortet.
9

[Umgeschrieben gemäß dem Kommentar von @PierredeLESPINAY.]

Dies ist nicht nur ein Nebeneffekt, sondern auch eine sehr nützliche Funktion , um die Logik Ihres Programms dynamisch zu ändern . Es stammt aus den prozeduralen PHP-Tagen, kann aber auch bei OO-Architekturen nützlich sein, wenn Sie alternative Implementierungen für bestimmte eigenständige Funktionen auf möglichst einfache Weise bereitstellen möchten. (Während OO die meiste Zeit die bessere Wahl ist, ist es eine Option, kein Mandat, und einige einfache Aufgaben benötigen keine zusätzliche Cruft.)

Wenn Sie beispielsweise Plugins dynamisch / bedingt aus Ihrem Framework laden und das Leben der Plugin-Autoren sehr einfach gestalten möchten, können Sie Standardimplementierungen für einige wichtige Funktionen bereitstellen, die das Plugin nicht überschrieben hat:

<?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... ;)
        }
    }
}
Gr.
quelle
2
Laut OP scheint der verschachtelte Funktionsumfang jedoch nicht auf die Containerfunktion beschränkt zu sein ...
Pierre de LESPINAY
1
@PierredeLESPINAY: Ups, sehr wahr, vielen Dank für den Hinweis! : -o Ich habe die Antwort entsprechend aktualisiert (umgeschrieben). (Dh es ist immer noch eine sehr praktische Funktion, aber aus einem ganz anderen Grund.)
Gr.
7

Funktionen, die in Funktionen definiert sind, für die ich nicht viel Verwendung sehe, aber bedingt definierte Funktionen, die ich kann. Zum Beispiel:

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

Und alles, was Ihr Code tun muss, ist die Verwendung der 'cmp'-Funktion in Dingen wie usort () -Aufrufen, damit Sie nicht den gesamten Code mit Wurfsprachen überprüfen. Jetzt habe ich das nicht getan, aber ich kann Argumente dafür sehen.

Cletus
quelle
1
Früher hätten wir diesen selbstmodifizierenden Code genannt. Ein großartiges Werkzeug, aber genauso gefährlich wie GOTO für Missbrauch ...
Killroy
2
Schlechte Idee. Besser: Verwenden Sie OO und hacken Sie nicht in die Details der Scripting-Engine.
Zanlok
1
Beachten Sie, dass variabel zugewiesene anonyme Funktionen deaktiviert werden können.
BF
4

Wenn dies alles gesagt ist, kann man einfach eine verschachtelte Funktion erstellen, um lokalisierten, sich wiederholenden Code innerhalb einer Funktion zu ersetzen (der nur innerhalb der übergeordneten Funktion verwendet wird). Eine anonyme Funktion ist ein perfektes Beispiel dafür.

Einige mögen sagen, nur private Methoden (oder kleinere Codeblöcke) in einer Klasse zu erstellen, aber das trübt das Wasser, wenn eine ultra-spezifische Aufgabe (die ausschließlich dem übergeordneten Element vorbehalten ist) modularisiert werden muss, aber für den Rest der Klasse nicht unbedingt verfügbar ist eine Klasse. Die gute Nachricht ist, wenn sich herausstellt, dass Sie diese Funktion woanders benötigen, ist die Korrektur eher elementar (verschieben Sie die Definition an einen zentraleren Ort).

Im Allgemeinen ist die Verwendung von JavaScript als Standard für die Bewertung anderer C-basierter Programmiersprachen eine schlechte Idee. JavaScript ist im Vergleich zu PHP, Python, Perl, C, C ++ und Java definitiv ein eigenes Tier. Natürlich gibt es viele allgemeine Ähnlichkeiten, aber die Details (Referenz- JavaScript: The Definitive Guide, 6. Ausgabe, Kapitel 1-12 ) machen das Kern-JavaScript einzigartig, schön, anders, einfach und komplex zugleich. Das sind meine zwei Cent.

Um ganz klar zu sein, ich sage nicht, dass verschachtelte Funktionen privat sind. Nur diese Verschachtelung kann helfen, Unordnung zu vermeiden, wenn etwas Triviales modularisiert werden muss (und nur von der übergeordneten Funktion benötigt wird).

Anthony Rutledge
quelle
2

Alle meine PHP ist OO, aber ich sehe eine Verwendung für verschachtelte Funktionen, insbesondere wenn Ihre Funktion rekursiv und nicht unbedingt ein Objekt ist. Das heißt, es wird nicht außerhalb der Funktion aufgerufen, in der es verschachtelt ist, sondern ist rekursiv und muss anschließend eine Funktion sein.

Es macht wenig Sinn, eine neue Methode für die ausdrückliche Verwendung einer einzelnen anderen Methode zu entwickeln. Für mich ist das ungeschickter Code und irgendwie nicht der Sinn von OO. Wenn Sie diese Funktion nirgendwo anders aufrufen, verschachteln Sie sie.

Jesse James Richard
quelle
1
Sie haben ziemlich viel Geld, aber ich denke, ein besseres Beispiel wäre das Deklarieren von Rückruffunktionen für array_filter (), array_map (), preg_replace_callback (), uasort () und dergleichen. Ich verwende diese Funktionen mit einer angemessenen Häufigkeit, und selten brauche ich den Rückruf, den ich außerhalb der OOP-Methode deklariere, von der ich ihn aufrufe. Daher fühlt es sich viel sauberer an, den globalen oder sogar Klassennamensraum nicht mit der Rückruffunktion zu verschmutzen . Und das kann ich endlich mit PHP 5.3 (wie in der Antwort von user614643 erklärt)!
Derek
1

Beim Aufrufen von Webservices wurde ein viel geringerer Overhead (Speicher und Geschwindigkeit) festgestellt, der dynamisch einzelne Funktionen über Bibliotheken mit Tausenden von Funktionen verschachtelt einschließt. Der typische Aufrufstapel kann zwischen 5 und 10 Anrufe tief sein, wobei nur die dynamische Verknüpfung von einem Dutzend Dateien mit 1 bis 2 KB besser ist als das Einschließen von Megabyte. Dies wurde nur durch Erstellen einer kleinen Util-Funktion erreicht, die für das Umschließen erforderlich ist. Die enthaltenen Funktionen werden in den Funktionen über dem Aufrufstapel verschachtelt. Betrachten Sie es im Gegensatz zu Klassen voller Funktionen, die nicht bei jedem Webservice-Aufruf erforderlich waren, sondern auch die eingebauten Lazy-Loading-Funktionen von PHP hätten verwenden können.

ZhuLien
quelle
1

Wenn Sie sich in PHP 7 befinden, sehen Sie Folgendes: Diese Implementierung gibt Ihnen eine klare Vorstellung von der verschachtelten Funktion. Angenommen, wir haben drei Funktionen (too (), boo () und zoo ()), die in der Funktion foo () verschachtelt sind. boo () und zoo () haben die gleichnamige verschachtelte Funktion xoo (). In diesem Code habe ich die Regeln für verschachtelte Funktionen klar auskommentiert.

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

    }

Wenn wir nun test1 () aufrufen, lautet die Ausgabe wie folgt:

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

Wenn wir test2 () aufrufen, lautet die Ausgabe wie folgt:

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

Wir können jedoch nicht gleichzeitig text1 () und test2 () aufrufen, um Fehler bei der erneuten Deklaration zu vermeiden

princebillyGK
quelle
Dies wäre leichter zu lesen und zu verdauen, wenn die Funktionsnamen eine eindeutige Eigenschaft jeder Funktion widerspiegeln würden, anstatt willkürliche, reimende, ähnlich aussehende Namen. Es ist verwirrend und schwierig, den Überblick zu behalten. Die Auswahl von Namen, die uns helfen, den Überblick zu behalten, wo sich die einzelnen Namen befinden, würde dies benutzerfreundlich machen und die kognitive Belastung verringern, die zum Lesen und Verstehen erforderlich ist. Ich habe nicht die Zeit oder den Willen zur Bestrafung, um diesen Beitrag zu durchlaufen, obwohl Sie wahrscheinlich einen großen Punkt darin versteckt haben, vermute ich, dass nur wenige hier bleiben werden, um ihn auszuheben. Das Lesen von SO ist kein Forschungsprojekt. KISS
SherylHohman
0

Ich weiß, dass dies ein alter Beitrag ist, aber ich verwende verschachtelte Funktionen, um einem rekursiven Aufruf einen ordentlichen Ansatz zu geben, wenn ich die Funktionalität nur lokal benötige - z. B. zum Erstellen hierarchischer Objekte usw. (offensichtlich müssen Sie vorsichtig sein, dass nur die übergeordnete Funktion vorhanden ist einmal genannt):

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
}

Ein wichtiger Punkt in PHP im Vergleich zu JS ist beispielsweise, dass der Aufruf der verschachtelten Funktion nach, dh nach der Funktionsdeklaration, erfolgen muss (im Vergleich zu JS, wo der Funktionsaufruf an einer beliebigen Stelle innerhalb der übergeordneten Funktion erfolgen kann


quelle
0

Ich habe dieses Merkmal nur dann wirklich verwendet, wenn es nützlich war, eine kleine rekursive Funktion innerhalb einer primären, kategorischeren Funktion auszuführen, wollte sie aber nicht in eine andere Datei verschieben, da sie für das Verhalten eines primären Prozesses von grundlegender Bedeutung war. Mir ist klar, dass es andere "Best Practice" -Methoden gibt, aber ich möchte sicherstellen, dass meine Entwickler diese Funktion jedes Mal sehen, wenn sie meinen Parser betrachten. Es ist wahrscheinlich, was sie sowieso ändern sollten ...

MJHd
quelle
-1

Verschachtelte Funktionen sind in Memoization nützlich (Ergebnisse der Caching-Funktion zur Verbesserung der Leistung).

<?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);
}
?>
Paul
quelle
3
Ich mag diese Idee im Prinzip, aber ich denke nicht, dass dies für Funktionen funktioniert, die Argumente akzeptieren, und Sie zwischenspeichern basierend auf diesen Argumenten. Wenn die Funktion ein zweites Mal mit unterschiedlichen Argumenten aufgerufen _foo()wird , wurde das Ergebnis nicht zwischengespeichert, und Sie werden versuchen, es erneut zu deklarieren, was zu einem schwerwiegenden Fehler führt.
MrWhite
-1

Verschachtelte Funktionen sind nützlich, wenn die verschachtelte Funktion eine Variable verwenden soll, die in der übergeordneten Funktion deklariert wurde.

<?php
ParentFunc();
function ParentFunc()
{
  $var = 5;
  function NestedFunc()
  {
    global $var;
    $var = $var + 5;
    return $var;
  };
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
}
?>
Matthew
quelle
2
So sollten Sie NIEMALS vorgehen.
Denis V
1
@fiddler, da NestedFunc nicht wirklich verschachtelt ist, wird es global.
Denis V