Do czego służą zagnieżdżone funkcje PHP?

80

W JavaScript funkcje zagnieżdżone są bardzo przydatne: domknięcia, metody prywatne i co masz ..

Do czego służą zagnieżdżone funkcje PHP? Czy ktoś ich używa i po co?

Oto małe dochodzenie, które przeprowadziłem

<?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
źródło
1
Mógłbym przysiąc, że przeczytałem, że wsparcie dla tego zostało porzucone w PHP6, ale nie mogę go nigdzie znaleźć.
Greg
2
@greg Myślałem, że cały plan dotyczący PHP6 i tak zawisł w powietrzu?
James,
Są świetne do dużych funkcji
trochę
Masz też zamknięcia w PHP, bez problemu.
Gralgrathor

Odpowiedzi:

88

W zasadzie nie ma. Zawsze traktowałem to jako efekt uboczny parsera.

Eran Galperin myli się, sądząc, że te funkcje są w jakiś sposób prywatne. Są po prostu niezadeklarowane, dopóki nie outer()zostaną uruchomione. Nie mają też prywatnego zasięgu; zanieczyszczają zasięg globalny, choć są opóźnione. Jako wywołanie zwrotne, zewnętrzne wywołanie zwrotne nadal można było wywołać tylko raz. Nadal nie widzę, jak pomocne jest zastosowanie go do tablicy, która najprawdopodobniej wywołuje alias więcej niż raz.

Jedynym przykładem „prawdziwego świata”, który mógłbym wykopać, jest ten , który można uruchomić tylko raz i można go przepisać jako czystszy, IMO.

Jedyne zastosowanie, jakie przychodzi mi do głowy, to wywoływanie przez moduły [name]_includemetody, która ustawia kilka zagnieżdżonych metod w przestrzeni globalnej, w połączeniu z

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

czeki.

OOP PHP byłoby oczywiście lepszym wyborem :)

Martijn Laarman
źródło
9
Tak, naprawdę. To brutalnie złe.
Tony Arkles
1
Świetny przykładowy link. Powinienem zacząć wdrażać to zamiast dziedziczenia!
zanlok
to samo co defdeklaracje w Rubim
user102008
1
Mimo że nie są one dokładnie funkcjami prywatnymi, nadal NIE można ich nazwać, chyba że wywoływana jest funkcja zewnętrzna, co daje im pewien rodzaj zależności jako funkcji, która ma być uruchamiana "w połączeniu" z funkcją zewnętrzną ...
techexpert
2
Bez tego zachowania automatyczne ładowanie nie działałoby. Jeśli deklaracje wewnątrz funkcji są w jakiś sposób prywatne, to dołączanie / wymaganie wykonywane przez program obsługi automatycznego ładowania nie robi nic.
cleong
88

Jeśli używasz PHP 5.3, możesz uzyskać bardziej podobne do Javacript zachowanie dzięki funkcji anonimowej:

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

Wynik:

test
test
user641463
źródło
11
+1 za faktyczną odpowiedź na (w zasadzie) funkcjonalny temat za pomocą funkcjonalnej odpowiedzi, a nie OOP
Peter Host
Autor pytania powinien zaktualizować zaakceptowane pytanie do tego. To właśnie odpowiada na to pytanie do moich czasów.
9

[Przepisano zgodnie z komentarzem @PierredeLESPINAY.]

To nie tylko efekt uboczny, ale w rzeczywistości bardzo przydatna funkcja do dynamicznego modyfikowania logiki twojego programu. Pochodzi z czasów proceduralnych PHP, ale może się również przydać w architekturach OO, jeśli chcesz zapewnić alternatywne implementacje niektórych samodzielnych funkcji w najprostszy możliwy sposób. (Chociaż OO jest lepszym wyborem przez większość czasu, jest to opcja, a nie mandat, a niektóre proste zadania nie wymagają dodatkowego okrucieństwa).

Na przykład, jeśli dynamicznie / warunkowo ładujesz wtyczki ze swojego frameworka i chcesz, aby życie autorów wtyczek było super łatwe, możesz zapewnić domyślne implementacje dla niektórych krytycznych funkcji, których wtyczka nie nadpisała:

<?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.
źródło
2
Jednak zgodnie z OP, zakres funkcji zagnieżdżonych wydaje się nie być ograniczony do funkcji kontenera ...
Pierre de LESPINAY
1
@PierredeLESPINAY: Ups, bardzo prawdziwe, wielkie dzięki za zwrócenie uwagi! : -o Odpowiednio zaktualizowałem (przepisałem) odpowiedź. (Tzn. To wciąż bardzo przydatna funkcja, ale wtedy z zupełnie innego powodu.)
Sz.
7

Funkcje zdefiniowane w funkcjach, dla których nie widzę zbytniego zastosowania, ale funkcje zdefiniowane warunkowo - widzę. Na przykład:

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

A potem wszystko, co musi zrobić twój kod, to użyć funkcji 'cmp' w takich rzeczach, jak wywołania usort (), aby nie zaśmiecać sprawdzania języka w całym kodzie. Nie zrobiłem tego, ale widzę argumenty przemawiające za tym.

cletus
źródło
1
Dawniej nazywalibyśmy ten samomodyfikujący się kod. Świetne narzędzie, ale równie niebezpieczne jak GOTO do nadużyć ...
Killroy
2
Zły pomysł. Lepiej: używaj OO i nie włamuj się do szczegółów silnika skryptowego.
zanlok
1
Uwaga - istnieje możliwość anulowania funkcji anonimowych przypisanych do zmiennych.
BF
4

Biorąc pod uwagę powyższe, można po prostu utworzyć zagnieżdżoną funkcję, aby zastąpić jakiś zlokalizowany, powtarzalny kod w funkcji (który będzie używany tylko wewnątrz funkcji nadrzędnej). Funkcja anonimowa jest tego doskonałym przykładem.

Niektórzy mogą powiedzieć, że po prostu utwórz metody prywatne (lub mniejsze bloki kodu) w klasie, ale to jest mętne, gdy bardzo specyficzne zadanie (które jest wyłączne dla rodzica) musi zostać zmodularyzowane, ale niekoniecznie dostępne dla reszty Klasa. Dobra wiadomość jest taka, że ​​jeśli okaże się, że potrzebujesz tej funkcji gdzie indziej, poprawka jest raczej elementarna (przenieś definicję do bardziej centralnej lokalizacji).

Ogólnie rzecz biorąc, używanie JavaScript jako standardu do oceny innych języków programowania opartych na C jest złym pomysłem. JavaScript jest zdecydowanie własnym zwierzęciem w porównaniu z PHP, Pythonem, Perlem, C, C ++ i Javą. Oczywiście istnieje wiele podobieństw, ale drobiazgowe szczegóły (referencyjne JavaScript: The Definitive Guide, 6th Edition, Rozdziały 1-12 ) sprawiają, że rdzeń JavaScript jest wyjątkowy, piękny, inny, prosty i złożone w tym samym czasie. To moje dwa centy.

Żeby było jasne, nie twierdzę, że funkcje zagnieżdżone są prywatne. Właśnie to zagnieżdżanie może pomóc uniknąć bałaganu, gdy coś trywialnego wymaga modularyzacji (i jest potrzebne tylko funkcji nadrzędnej).

Anthony Rutledge
źródło
2

Całe moje php to OO, ale widzę zastosowanie dla funkcji zagnieżdżonych, szczególnie gdy twoja funkcja jest rekurencyjna i niekoniecznie jest obiektem. Oznacza to, że nie jest wywoływana poza funkcją, w której jest zagnieżdżona, ale jest rekurencyjna, a następnie musi być funkcją.

Tworzenie nowej metody do ekspresowego użycia jednej innej metody nie ma sensu. Dla mnie to niezgrabny kod i raczej nie jest to celem OO. Jeśli nigdy nie będziesz wywoływać tej funkcji nigdzie indziej, zagnieżdż ją.

Jesse James Richard
źródło
1
Masz dość dużo pieniędzy, ale myślę, że lepszym przykładem byłoby deklarowanie funkcji wywołania zwrotnego dla array_filter (), array_map (), preg_replace_callback (), uasort () i tym podobnych. Używam tych funkcji z dużą częstotliwością i rzadko potrzebuję wywołania zwrotnego, z którego deklaruję, poza metodą OOP, z której ją wywołuję, więc wydaje się o wiele czystsze, aby uniknąć zanieczyszczania globalnej lub nawet przestrzeni nazw klas funkcją wywołania zwrotnego . I wreszcie mogę to zrobić z PHP 5.3 (jak wyjaśniono w odpowiedzi użytkownika614643)!
Derek,
1

Podczas wywoływania usług sieciowych okazało się, że jest to znacznie niższe obciążenie (pamięć i szybkość) dynamicznie, w tym w sposób zagnieżdżony, poszczególne funkcje w bibliotekach pełnych tysięcy funkcji. Typowy stos wywołań może mieć głębokość od 5 do 10 wywołań, tylko wymaganie dynamicznego łączenia kilkunastu plików o wielkości 1–2 KB było lepsze niż uwzględnienie megabajtów. Dokonano tego po prostu przez utworzenie niewielkiej funkcji opakowującej, której wymaga. Dołączone funkcje zostaną zagnieżdżone w funkcjach powyżej stosu wywołań. Rozważ to w przeciwieństwie do klas pełnych setek funkcji, które nie były wymagane przy każdym wywołaniu usługi sieciowej, ale mogły również wykorzystywać wbudowane funkcje php z opóźnionym ładowaniem.

ZhuLien
źródło
1

jeśli jesteś w php 7, zobacz to: Ta implementacja da ci jasny obraz funkcji zagnieżdżonych. Załóżmy, że mamy trzy funkcje (too (), boo () i zoo ()) zagnieżdżone w funkcji foo (). boo () i zoo () mają tę samą nazwaną zagnieżdżoną funkcję xoo (). Teraz w tym kodzie wyraźnie zakomentowałem reguły funkcji zagnieżdżonych.

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

    }

Teraz, jeśli wywołamy test1 (), wynik będzie taki:

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

jeśli wywołasz test2 (), wynik będzie taki:

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

Ale nie możemy wywołać jednocześnie text1 () i test2 (), aby uniknąć błędu ponownej deklaracji

princebillyGK
źródło
Byłoby to łatwiejsze do odczytania i zrozumienia, gdyby nazwy funkcji odzwierciedlały pewne unikalne cechy każdej funkcji, a nie arbitralne, rymowane, podobnie wyglądające nazwy. Jest to zagmatwane i trudne do śledzenia. Wybór nazw, które pomogą nam śledzić, gdzie każdy z nich się znajduje, uczyniłby to przyjaznym dla użytkownika i zmniejszyłby obciążenie poznawcze wymagane do czytania i rozumienia. Nie mam czasu ani woli ukarania, aby przejść przez ten post, chociaż prawdopodobnie masz tam ukryty wielki punkt, podejrzewam, że niewielu zostanie, aby go wykopać. Czytanie SO nie jest projektem badawczym. KISS
SherylHohman
0

Wiem, że to stary post, ale fwiw Używam funkcji zagnieżdżonych, aby zapewnić schludne i uporządkowane podejście do wywołań rekurencyjnych, gdy potrzebuję funkcji tylko lokalnie - np. Do budowania obiektów hierarchicznych itp. (Oczywiście musisz uważać, że funkcja nadrzędna jest tylko zadzwoniono raz):

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
}

Punktem uwagi w php w porównaniu z JS na przykład jest to, że wywołanie funkcji zagnieżdżonej musi być wykonane po, tj. Poniżej, deklaracji funkcji (w porównaniu z JS gdzie wywołanie funkcji może być gdziekolwiek w funkcji nadrzędnej


źródło
0

Tak naprawdę użyłem tej cechy tylko wtedy, gdy przydatne było wykonanie małej funkcji rekurencyjnej wewnątrz podstawowej, bardziej kategorycznej funkcji, ale nie chciałem przenosić jej do innego pliku, ponieważ było to fundamentalne dla zachowania podstawowego procesu. Zdaję sobie sprawę, że istnieją inne sposoby realizacji tego zadania, ale chcę się upewnić, że moi programiści widzą tę funkcję za każdym razem, gdy patrzą na mój parser, prawdopodobnie i tak powinni zmodyfikować ...

MJHd
źródło
-1

Zagnieżdżone funkcje są przydatne w Memoization (buforowanie wyników funkcji w celu poprawy wydajności).

<?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);
}
?>
Paweł
źródło
3
Zasadniczo podoba mi się ten pomysł, jednak nie sądzę, aby zadziałał na funkcjach, które akceptują argumenty, a buforowanie na podstawie tych argumentów. Gdy funkcja jest wywoływana po raz drugi, z różnymi argumentami , wynik nie zostanie zapisany w pamięci podręcznej i spróbujesz ponownie zadeklarować, _foo()co spowoduje błąd krytyczny.
MrWhite
-1

Funkcje zagnieżdżone są przydatne, jeśli chcesz, aby funkcja zagnieżdżona korzystała ze zmiennej zadeklarowanej w funkcji nadrzędnej.

<?php
ParentFunc();
function ParentFunc()
{
  $var = 5;
  function NestedFunc()
  {
    global $var;
    $var = $var + 5;
    return $var;
  };
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
}
?>
Mateusz
źródło
2
W ten sposób NIGDY nie powinieneś robić.
Denis V
1
@fiddler, ponieważ NestedFunc nie jest tak naprawdę zagnieżdżony, staje się globalny.
Denis V,