Для чего нужны вложенные функции 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 в любом случае висит в воздухе?
Джеймс
Они отлично подходят для больших функций - вроде рекурсивной организации
JVE999
У вас тоже есть закрытие в PHP, не беспокойтесь.
Gralgrathor

Ответы:

88

Нет в принципе. Я всегда рассматривал это как побочный эффект парсера.

Эран Гальперин ошибается, полагая, что эти функции в некотором роде частные. Они просто не декларируются, пока не outer()будут запущены. Они также не находятся в частной области видимости; они действительно загрязняют глобальный масштаб, хотя и с задержкой. И в качестве обратного вызова внешний обратный вызов все еще может быть вызван только один раз. Я до сих пор не понимаю, насколько полезно применять его к массиву, который, скорее всего, вызывает псевдоним более одного раза.

Единственный пример «реального мира», который я мог выкопать, - это этот , который может запускаться только один раз и может быть переписан более чистым, IMO.

Единственное использование, которое я могу придумать, - это вызов модулями [name]_includeметода, который устанавливает несколько вложенных методов в глобальном пространстве в сочетании с

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

чеки.

ООП PHP, очевидно, был бы лучшим выбором :)

Мартейн Лаарман
источник
9
Ага, правда. Это ужасно плохо.
Тони Арклес,
1
Отличный пример ссылки. Я должен начать реализовывать это вместо наследования!
zanlok
то же, что и defобъявления в Ruby
user102008
1
Несмотря на то, что они не совсем частные функции, они все равно НЕ могут быть вызваны, ЕСЛИ не вызывается внешняя функция, поэтому это дает им своего рода зависимость как функцию, которая
запускается
2
Без этого поведения автозагрузка не работала бы. Если объявления внутри функции каким-то образом являются частными, то включение / требование, выполняемое вашим обработчиком автозагрузки, в конечном итоге ничего не сделает.
cleong
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
user641463
источник
11
+1 за ответ на (в основном) функциональную тему функциональным ответом, а не ООП
Питер Хост
Автор вопроса должен заменить принятый вопрос на этот. Вот что собственно и отвечал на вопрос до моего времени.
9

[Переписано в соответствии с комментарием @PierredeLESPINAY.]

Это вообще не просто побочный эффект, а на самом деле очень полезная функция для динамического изменения логики вашей программы. Это из процедурных дней PHP, но может пригодиться и с объектно-ориентированной архитектурой, если вы хотите предоставить альтернативные реализации для определенных автономных функций наиболее простым способом. (Хотя в большинстве случаев объектно-ориентированный подход является лучшим выбором, это вариант, а не поручение, и некоторые простые задачи не требуют лишних хлопот.)

Например, если вы динамически / условно загружаете плагины из своего фреймворка и хотите упростить жизнь авторам плагинов, вы можете предоставить реализации по умолчанию для некоторых критических функций, которые плагин не переопределял:

<?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.
источник
2
Однако, согласно OP, объем вложенной функции, похоже, не ограничивается функцией контейнера ...
Пьер де ЛЕСПИНАЙ
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 (), чтобы вы не засоряли языковые проверки по всему коду. Сейчас я этого не делал, но вижу аргументы в пользу этого.

Cletus
источник
1
Раньше мы бы назвали этот самомодифицирующийся код. Отличный инструмент, но такой же опасный, как GOTO для злоупотреблений ...
Киллрой
2
Плохая идея. Лучше: используйте объектно-ориентированный подход и не взламывайте подробности скриптового движка.
zanlok
1
Имейте в виду - можно отключить переменную, назначенную анонимным функциям.
BF
4

При всем вышесказанном можно просто создать вложенную функцию для замены некоторого локализованного повторяющегося кода внутри функции (который будет использоваться только внутри родительской функции). Анонимная функция - прекрасный пример этого.

Кто-то может сказать, что просто создайте частные методы (или меньшие блоки кода) в классе, но это запутывает воду, когда сверхспецифическая задача (которая является эксклюзивной для родителя) должна быть модульной, но не обязательно доступной для остальной части класс. Хорошая новость заключается в том, что если окажется, что эта функция вам нужна где-то еще, исправление будет довольно элементарным (переместите определение в более центральное место).

Вообще говоря, использование JavaScript в качестве стандарта для оценки других языков программирования на основе C - плохая идея. JavaScript определенно является самостоятельным животным по сравнению с PHP, Python, Perl, C, C ++ и Java. Конечно, есть много общего, но мелкие мелкие детали (ссылка на JavaScript: The Definitive Guide, 6-е издание, главы 1-12 ), если на них обратить внимание, сделают ядро ​​JavaScript уникальным, красивым, разным, простым и сложны все одновременно. Это мои два цента.

Чтобы быть ясным, я не говорю, что вложенные функции являются частными. Именно такое вложение может помочь избежать беспорядка, когда что-то тривиальное нужно модулировать (а это необходимо только родительской функции).

Энтони Ратледж
источник
2

Весь мой php - объектно ориентированный объект, но я вижу использование вложенных функций, особенно когда ваша функция является рекурсивной и не обязательно является объектом. Другими словами, он не вызывается вне функции, в которую он вложен, но является рекурсивным и, следовательно, должен быть функцией.

Нет смысла создавать новый метод для экспресс-использования одного другого метода. Для меня это неуклюжий код и не суть объектно-ориентированного подхода. Если вы никогда не собираетесь вызывать эту функцию где-либо еще, вложите ее.

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

При вызове веб-сервисов мы обнаружили гораздо меньшие накладные расходы (память и скорость), динамически включая вложенные функции, отдельные функции над библиотеками, полными тысячей функций. Типичный стек вызовов может составлять от 5 до 10 вызовов в глубину, требуя динамического связывания дюжины файлов размером 1-2 КБ, чем включение мегабайт. Это было сделано просто путем создания небольшой обертки служебной функции. Включенные функции становятся вложенными в функции над стеком вызовов. Рассмотрим это в отличие от классов, полных сотен функций, которые не требовались при каждом вызове веб-службы, но также могли использовать встроенные функции отложенной загрузки php.

ZhuLien
источник
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 (), чтобы избежать ошибки повторного объявления.

princebillyGK
источник
Это было бы легче читать и переваривать, если бы имена функций отражали некоторые уникальные характеристики каждой функции, а не произвольные, рифмующиеся, похожие на вид имена. Это сбивает с толку, и за ним сложно уследить. Выбор имен, которые помогут нам отслеживать, где находится каждый из них, сделает его удобным для пользователя и снизит когнитивную нагрузку, необходимую для чтения и понимания. У меня нет времени или воли к наказанию, чтобы пройти через этот пост, хотя у вас, вероятно, есть большая точка, спрятанная там, я подозреваю, что немногие останутся там, чтобы раскопать ее. Чтение SO - это не исследовательский проект. KISS
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
}

Следует отметить, что в php по сравнению с JS, например, вызов вложенной функции должен выполняться после, то есть ниже, объявления функции (по сравнению с JS, где вызов функции может быть где угодно в родительской функции


источник
0

Я действительно использовал эту характеристику только тогда, когда было полезно выполнить небольшую рекурсивную функцию внутри основной, более категориальной функции, но я не хотел перемещать ее в другой файл, потому что это было фундаментальным для поведения основного процесса. Я понимаю, что есть и другие "лучшие практики" для этого, но я хочу, чтобы мои разработчики видели эту функцию каждый раз, когда смотрят на мой синтаксический анализатор, вероятно, что они все равно должны изменить ...

MJHd
источник
-1

Вложенные функции полезны в Memoization (кэширование результатов функции для повышения производительности).

<?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
Так поступать НИКОГДА нельзя.
Denis V
1
@fiddler, потому что NestedFunc на самом деле не вложен, он становится глобальным.
Denis V