Для чого призначені вкладені функції PHP?


80

У 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, немає поту.
Гралгратор,

Відповіді:


88

В основному такого немає. Я завжди сприймав це як побічний ефект парсера.

Еран Гальперін помиляється, вважаючи, що ці функції якимось чином приватні. Вони просто не оголошені, доки їх outer()не запустять. Вони також не підпадають під приватний приціл; вони забруднюють глобальний масштаб, хоча і затримується. І як зворотний виклик, зовнішній зворотний виклик все ще міг бути викликаний лише один раз. Я досі не бачу, як корисно застосовувати його до масиву, який, швидше за все, викликає псевдонім не раз.

Єдиний приклад "реального світу", який я міг би розкопати, - це той , який може працювати лише один раз і може бути переписаний на чистіший, IMO.

Єдине використання, яке я можу придумати, - це модулі для виклику [name]_includeметоду, який встановлює декілька вкладених методів у глобальному просторі в поєднанні з

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

перевірки.

OOP PHP, очевидно, був би кращим вибором :)


9
Так, справді. Це жорстоко погано.
Tony Arkles

1
Чудовий приклад посилання. Я повинен почати реалізовувати це замість спадкування!
zanlok

те саме, що і defдекларації в Ruby
user102008

1
Незважаючи на те, що це не зовсім приватні функції, їх все одно НЕ можна називати, ДО ЯКЩО не буде викликана зовнішня функція, тож це дає їм певну залежність як функції, яка запускається "спільно" із зовнішньою функцією ...
techexpert

2
Без такої поведінки автозавантаження не працювало. Якщо оголошення всередині функції є якимось приватним, тоді включення / вимагання, виконане вашим обробником автозавантаження, в кінцевому підсумку нічого не зробить.
Клеонг

88

Якщо ви використовуєте 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 за те, що фактично відповідав на (в основному) функціональну тему функціональною відповіддю, а не ООП
Пітер Хост

Автор запитання повинен оновити прийняте запитання до цього. Це те, що насправді відповідає на питання до мого часу.

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

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

І тоді все, що вам потрібно зробити, - це використовувати функцію 'cmp' у таких речах, як виклики usort (), щоб ви не смітили перевірки мови у всьому коді. Зараз я цього не робив, але бачу аргументи для цього.


1
Ще в ті часи ми б називали цей самомодифікуючий код. Чудовий інструмент, але такий же небезпечний, як GOTO для зловживань ...
Killroy

2
Погана ідея. Краще: використовуйте ОО, а не зламуйте деталі механізму сценаріїв.
zanlok

1
Майте на увазі - можна скасувати присвоєні анонімним функціям змінні.
BF

4

З огляду на все вищесказане, можна просто створити вкладену функцію для заміни якогось локалізованого повторюваного коду всередині функції (яка буде використовуватися лише всередині батьківської функції). Анонімна функція є прекрасним прикладом цього.

Хтось може сказати, просто створити приватні методи (або менші блоки коду) у класі, але це замучує воду, коли надто конкретне завдання (яке є виключним для батьків) потрібно модулювати, але не обов’язково доступне для решти клас. Хороша новина полягає в тому, що якщо виявиться, що ця функція вам потрібна десь ще, виправлення досить елементарне (перемістіть визначення в більш центральне місце).

Взагалі кажучи, використання JavaScript як стандарту, за яким оцінюються інші мови програмування на основі С, є поганою ідеєю. JavaScript, безумовно, є власною твариною в порівнянні з PHP, Python, Perl, C, C ++ та Java. Звичайно, є багато загальних подібностей, але дрібні, піщані подробиці (посилання на JavaScript: The Definitive Guide, 6-е видання, розділи 1-12 ), коли на них звертають увагу, роблять основний JavaScript унікальним, красивим, різним, простим та все одночасно. Це мої два центи.

Щоб зрозуміти, я не кажу, що вкладені функції є приватними. Саме це вкладання може допомогти уникнути безладу, коли щось тривіальне потрібно модулювати (і це потрібно лише батьківській функції).


2

Весь мій php - це OO, але я бачу використання вкладених функцій, особливо коли ваша функція рекурсивна і не обов'язково об'єкт. Тобто, він не викликається поза функцією, в яку він вкладений, але є рекурсивним і згодом повинен бути функцією.

Немає сенсу створювати новий метод для експресного використання одного іншого методу. Для мене це незграбний код і начебто не суть ОО. Якщо ви ніколи більше не збираєтеся викликати цю функцію, вкладіть її.


1
Ви в значній мірі на грошах, але я думаю, що кращим прикладом може бути декларування функцій зворотного виклику для array_filter (), array_map (), preg_replace_callback (), uasort () тощо. Я використовую ці функції з достатньою кількістю частот, і рідко мені потрібен зворотний виклик, який я оголошую, поза методом ООП, з якого я його викликаю, тому він відчуває себе набагато чистішим, щоб уникнути забруднення глобального або навіть простору імен класу за допомогою функції зворотного виклику . І нарешті я можу це зробити за допомогою PHP 5.3 (як пояснено у відповіді користувача user614643)!
Дерек

1

Під час викликів веб-служб ми виявили набагато менші накладні витрати (пам’ять і швидкість), які динамічно включають окремі функції над бібліотеками, наповненими 1000-ми функціями, вкладеними. Типовий стек дзвінків може бути між 5-10 дзвінками, лише вимагати динамічного зв’язку дюжини файлів розміром 1-2 кб було краще, ніж включати мегабайти. Це було зроблено лише шляхом створення невеликої утиліти, яка вимагає обтікання. Включені функції вкладаються в функції над стеком викликів. Розглянемо це на відміну від класів, повних 100-х функцій, які не потрібні під час кожного виклику веб-служби, але також могли б використовувати вбудовані функції лінивого завантаження php.


1

якщо ви перебуваєте у php 7, то перегляньте це: Ця реалізація дасть вам чітке уявлення про вкладену функцію. Припустимо, у нас є три функції (too (), boo () і zoo ()), вкладені у функцію foo (). 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

Я знаю, що це стара публікація, але fwiw я використовую вкладені функції, щоб надати акуратний і акуратний підхід до рекурсивного виклику, коли мені потрібна функціональність лише локально - наприклад, для побудови ієрархічних об'єктів тощо (очевидно, ви повинні бути обережними, лише батьківська функція дзвонив один раз):

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
}

Наприклад, примітка у 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
Мені ця ідея подобається в принципі, однак, я не думаю, що це буде працювати з функціями, які приймають аргументи, і ви кешуєте на основі цих аргументів. Коли функцію викликають вдруге, з різними аргументами , результат не буде кешовано, і ви спробуєте повторно оголосити, _foo()що призведе до фатальної помилки.
MrWhite

-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
Ось як ви НІКОЛИ не повинні робити.
Денис V

1
@fiddler, оскільки NestedFunc насправді не вкладений, він стає глобальним.
Денис V
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.