Анонимные функции в PHP

Анонимные функции часто используются в современных языках программирования, они популярны в Ruby, Javascript и Python. А в PHP до версии 5.3 не было истинно анонимных функций. Хотя для начинающих программистов сложно найти им применение, их использование это очень элегантное решение некоторых практических задач.

Переменные-функции в PHP

До того как сказать что-либо об анонимных функциях, взглянем на концепцию PHP известную как переменные-функции. Она заключается в том, что если добавить к переменной круглые скобки, то интерпретатор PHP сначала проверит, не существует ли функции с именем равным значению переменной и если она есть - выполнить ее. Это можно увидеть на следующем примере:

  1. function Hello($name)
  2. {
  3. echo "Привет, $name";
  4. }

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

  1. $func = "Hello";
  2. $func("Мир!");
  3.  
  4. // результат:
  5. //Привет, Мир!

Другой пример, используя класс и статический метод:

  1. <?php
  2. class CHello
  3. {
  4. static function Hello($name)
  5. {
  6. echo "Привет, $name";
  7. }
  8. }
  9. $func = "Hello";
  10. CHello::$func("Мир!");
  11.  
  12. // результат:
  13. // Привет, мир!
  14. ?>

Анонимные или лямбда функции

Бывает, необходимо создать небольшую локальную функцию, состоящую всего из нескольких строк, например, для обратного вызова. Глупо загрязнять глобальное пространство имен такого рода одноразовыми функциями. Гораздо лучше создать анонимную или лямбда функцию используя create_function. Анонимные функции позволяют создать функции у которых нет имени. Рассмотрим на примере:

  1. <?php
  2. $str = "Привет, Мир!";
  3. $lambda = create_function('$match', 'return "Друг!";');
  4. $str = preg_replace_callback('/Мир/', $lambda, $str);
  5. echo $str ;
  6. ?>

Здесь мы создали маленькую безымянную (анонимную) функцию, которая вызывается в функции preg_replace_callback. Хотя create_function позволяет создать анонимные функции, это не часть языка, а "хак". PHP 5.3 поддерживает по настоящему анонимные функции как часть языка. Мы создаем безымянную функцию и присваиваем её переменной и затем просто используем эту переменную как функцию. Пример:

  1. <?php
  2. $func = function($name)
  3. {
  4. echo "Привет, $name\n";
  5. };
  6.  
  7. $func("Мир!");
  8. $func("Алексей!");
  9.  
  10. // результат:
  11. //Привет, Мир!
  12. //Привет, Алексей!
  13. ?>

Заметьте, что в конце определения функции есть точка с запятой. Она необходима, так как такое определение функции это утверждение, а после определения утверждения всегда должна быть точка с запятой. Еще один пример:

  1. <?php
  2. $str = "Привет, Мир!";
  3. $func = function($match)
  4. {
  5. return "друг!";
  6. };
  7.  
  8. $str = preg_replace_callback('/Мир/', $func, $str);
  9. echo $str ;
  10. ?>

Анонимные и вложенные функции

PHP позволяет вкладывать функции внутрь друг друга. Хотя это выглядит скорее подобно побочному эффекту парсера, чем намеренному решению, но зато очень помогает в некоторых случаях. Рассмотрим на примере. Функция censorString принимает строку как параметр и заменяет нужные слова в строке на "*". Функция censorString определяет вложенную функцию replace, которая используется при вызове preg_replace_callback. Учитывая то, что функция replace используется в нашей программе только в функции censorString, лучше всего определить ее как вложенную, чтобы не загрязнять пространство имен.

  1. <?php
  2. function censorString($string, $censor)
  3. {
  4. function replace($match)
  5. {
  6. return str_repeat("*", strlen($match[0]));
  7. }
  8.  
  9. return preg_replace_callback('/'.$censor.'/', 'replace', $string);
  10.  
  11. }
  12.  
  13. echo censorString("Привет, мир!", "мир");
  14. echo censorString("Привет, мир!", "Привет");
  15.  
  16. // результат:
  17. // Привет, ***!
  18. // Fatal error: Cannot redeclare replace()
  19. ?>

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

  1. <?php
  2. function censorString($string, $censor)
  3. {
  4. $func = function($match)
  5. {
  6. return str_repeat("*", strlen($match[0]));
  7. };
  8.  
  9. return preg_replace_callback('/'.$censor.'/', $func, $string);
  10.  
  11. }
  12.  
  13. echo censorString("Привет, мир!", "мир");
  14. echo censorString("Привет мир!", "Привет");
  15.  
  16. // результат:
  17. // Привет, ***!
  18. // ******, мир!
  19. ?>

Теперь вложенная функция появляется только на время выполнения censorString. Итак, мы можем повторно вызывать функцию censorString без того, чтобы вызвать ошибку передекларации.

Другой способ определить вложенную функцию:

  1. <?php
  2. function censorString($string, $censor)
  3. {
  4.  
  5. return preg_replace_callback('/'.$censor.'/',
  6. function($match)
  7. {
  8. return str_repeat("*",
  9. strlen($match[0]));
  10. },
  11. $string);
  12.  
  13. }
  14.  
  15. echo censorString("Привет, мир!", "мир");
  16. echo censorString("Привет, мир!", "Привет");
  17.  
  18. // результат:
  19. // Привет, ***!
  20. // ******, мир!
  21. ?>

Замыкания

Замыкание это анонимная функция со своим контекстом. Короче говоря, это функция, которая знает о переменных, не определенных в ней. Рассмотрим на простом примере. Скажем, мы хотим создать анонимную функцию, которая умножает число на 5.

  1. <?php
  2. $mult = function($x)
  3. {
  4. return $x * 5;
  5. };
  6.  
  7. echo $mult(2);
  8.  
  9. // результат:
  10. // 10
  11. ?>

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

  1. <?php
  2. $multiply = function($multiplier)
  3. {
  4. return function($x) use ($multiplier)
  5. {
  6. return $x * $multiplier;
  7. };
  8. };
  9.  
  10. // $mul5 теперь содержит функцию, которая умножает аргумент на пять
  11. $mult5 = $multiply(5);
  12.  
  13. // $mul7 теперь содержит функцию, которая умножает аргумент на семь
  14. $mult7 = $multiply(7);
  15.  
  16. echo $mult5(5);
  17. echo $mult7(5);
  18.  
  19. // результат:
  20. // 25
  21. // 35
  22. ?>

Лямбда функции и замыкания приближают PHP по функциональности к другим современным языкам. И это хорошо :)

6 Responses to Анонимные функции в PHP

  1. gravatar

    Охренеть, $multiply синтаксис как в ЯваСкрипт. Ниразу так в пыхе не писал

  2. gravatar

    // $mul5 теперь содержит функцию, которая умножает аргумент на семь
    должно быть:
    // $mul7 теперь содержит функцию, которая умножает аргумент на семь

  3. gravatar

    @Валентин Сушков, спасибо, поправил

  4. gravatar

    Первая из статей в поиске гугла по соответствующему запросу, после которой я стал немного понимать, для чего все это.
    Спасибо.

  5. gravatar

    Спасибо, очень доступно

  6. gravatar

    ?????????? ??????! ???????? ?? ????!

Leave a Reply