Не секрет, что на собеседованиях любят задавать каверзные вопросы. Не всегда адекватные, не всегда имеющие отношение к реальности, но факт остается фактом — задают. Конечно, вопрос вопросу рознь, и иногда вопрос, на первый взгляд кажущийся вам дурацким, на самом деле направлен на проверку того, насколько хорошо вы знаете язык, на котором пишете.
image
Вторая часть серии статей посвящена одному из самых сложных и объемных вопросов о современном PHP — что такое «callable»? Я постарался свести в один текст некий минимум знаний об этом вопросе.


Что такое callable?


Callable — это специальный псевдотип данных в PHP, означающий «нечто, что может быть вызвано как функция». Как будет видно ниже, значения этого псевдотипа могут быть самых разных реальных типов, но всегда есть нечто, что их объединяет — это способность быть использованными в качестве функции.

А можно пример?


Да легко. Самый часто используемый в современном языке вариант callable — это анонимная функция.
$x = function ($a) {
  return $a * 2;
};

assert( 
  true === is_callable($x) 
);
assert( 
  4 == $x(2) 
);


Функция is_callable() как раз проверяет — принадлежит ли переданное ей значение псевдотипу callable. Разумеется, анонимная функция принадлежит этому псевдотипу и is_callable() вернёт true.

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

В PHP существует специальный системный класс Closure. Когда вы создаете новую анонимную функцию, по сути вы неявно создаете объект этого класса. Подробнее об этом можно прочитать в мануале: php.net/manual/ru/class.closure.php

Немного путаницы. Авторы версии 5.3, в которой впервые появился современный синтаксис анонимных функций, перепутали два понятия — собственно анонимная функция (лямбда-функция) и замыкание (замыкание переменной на контекст этой анонимной функции). Именно поэтому анонимные функции реализованы в языке с помощью системного класса Closure, а не, к примеру, Lambda, как стоило бы ожидать. Имейте этот факт в виду на собеседовании — многие интервьюеры сами путают понятия «лямбда-функция» и «замыкание». Впрочем, подробный рассказ о том, что такое «замыкание», выходит за рамки этой статьи.


Строка как callable и небольшая историческая справка


Строки в PHP вполне могут быть callable! В этом случае интерпретатор будет искать обычную, неанонимную функцию с именем, совпадающим с данной строкой и, в случае успеха, вызовет такую функцию.
function foo($bar) {
  return $bar * 2;
}

$x = 'foo';

assert(
  true === is_callable($x)
);
assert(
  4 == $x(2)
);


Таким образом можно вызывать как свои функции, так и библиотечные. Есть ряд ограничений — нельзя вызвать isset(), empty() и другие функции, которые фактически являются конструкциями языка.

Стоит заметить, что callable-строка может содержать в себе конструкцию вида 'ClassName::method' — это не возбраняется, такие строки тоже будут callable. Обратите внимание на особенность — скобки списка аргументов в таком случае не пишутся!
class Foo {
  public static function bar() {
    return 42;
  }
}

$x = 'Foo::bar';

assert( 
  true === is_callable($x) 
);
assert( 
  42 == call_user_func($x) 
);

Вторая особенность такой callable строки в том, что невозможно вызвать ее напрямую, с помощью $x(), мы получим ошибку вида «Fatal error: Call to undefined function Foo::bar()» И здесь нам на помощь приходит специальная функция call_user_func(), которая умеет обходить «острые углы» и вызывать значения псевдотипа callable, даже если это невозможно с помощью обычного синтаксиса.

Вас могут попытаться подловить вопросом — а как давно в PHP появились анонимные функции? Корректный ответ таков: «Современный синтаксис появился в версии 5.3, а ранее, со времен PHP 4, существовал способ создания анонимных функций с помощью функции create_function() Разумеется, сейчас этот способ имеет лишь исторический интерес. И должен у любого уважающего себя программиста вызывать такие же чувства, как оператор goto и функция eval() — желание никогда это не писать.»

Почему я пишу об этом казусе здесь? Дело в том, что на самом деле create_function() не создавала лямбда-функцию в современном понимании, фактически эта функция создавала именованную функцию с именем наподобие «lambda_1» и возвращала ее имя. А дальше работал уже знакомый нам механизм, когда string является callable


Callable массивы


Массивы в PHP тоже могут быть callable! Есть два основных случая, когда это работает. Проще всего показать их на примере:
class Foo {

  public static function bar() {
    return 42;
  }

  public function baz() {
    return 1.46;
  }

}

assert( 
  true === is_callable([Foo::class, 'bar']) 
);
assert( 
  42 == call_user_func([Foo::class, 'bar']) 
);

$x = new Foo;

assert( 
  true === is_callable([$x, 'baz']) 
);
assert( 
  1.46 == call_user_func([$x, 'baz']) 
);

Итак, массив, в котором нулевой элемент — это имя класса, а первый — имя статического метода, является callable. Ровно также, как и массив, состоящий из объекта и имени его динамического метода.

Стоит отметить интересную особенность (подметили читатели в комментариях). Если в классе Foo определен метод __call() или __callStatic(), то is_callable(Foo $foo, 'bar') или is_callable(Foo::class, 'bar') соответственно всегда будет true. Что, в общем-то, вполне логично.


Callable объекты


Да, в PHP возможно и такое. Объект вполне может быть «функцией», достаточно лишь определить в классе магический метод __invoke():
class Filter {

  protected $filter = FILTER_DEFAULT;

  public function setFilter($filter) {
    $this->filter = $filter;
  }

  public function __invoke($val) {
    return filter_var($val, $this->filter);
  }

}

$filter = new Filter();
$filter->setFilter(FILTER_SANITIZE_EMAIL);

assert( true === is_callable($filter) );
assert( 'foo@example.com' == $filter('f o o @ example . com') );

$filter->setFilter(FILTER_SANITIZE_URL);
assert( 'http://test.com' == $filter('http:// test . com') );

Метод __invoke() будет автоматически вызван при попытке использования объекта, как функции.

Тайп-хинтинг


В современных версиях PHP, начиная с 5.4, появилась возможность указывать псевдотип callable в качестве хинта типа аргумента функции.
function filter($value, callable $filter) {
  return $filter($value);
}

В случае, если переданное значение не будет callable, попытка вызова функции с таким аргументом приведет к фатальной ошибке «Catchable fatal error: Argument 2 passed to filter() must be callable»

Вместо заключения


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

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

И, разумеется, чтобы не упасть в грязь лицом на собеседовании :)

Продолжение следует!

Литература

php.net/manual/ru/language.types.callable.php
php.net/manual/ru/function.call-user-func.php
php.net/manual/ru/functions.anonymous.php
php.net/manual/ru/class.closure.php
php.net/manual/ru/language.oop5.magic.php#object.invoke
habrahabr.ru/post/147620
habrahabr.ru/post/167181
fabien.potencier.org/on-php-5-3-lambda-functions-and-closures.html

Комментарии (60)


  1. to0n1
    10.06.2015 18:43

    Спасибо за статью! Сам часто задаю вопросы о callable на собеседовании. К сожалению многие спотыкаются если вопрос начать именно зачем ввели такой странный магический метод __invoke.

    В вашем последнем примере есть неточность, как вы сами написали callable не всегда может быть вызвана через скобки, поэтому безопасно всегда использовать call_user_func


    1. AlexLeonov Автор
      10.06.2015 18:49

      Согласен, безопаснее именно через call_user_func(), но в своем собственном проекте позволительно и так. Если вы уверены, что никакой экзотики кроме нормальных лямбд в эту часть кода не просочится )))


    1. guyfawkes
      10.06.2015 20:02
      +2

      Повторюсь вопросом из прошлого топика — когда вы реально в повседневном коде используете __invoke?


      1. AlexLeonov Автор
        10.06.2015 20:58
        -5

        Ежедневно буквально, а что?

        Типа такого постоянно пишу:

        $cache = new Cache\Memcache;
        return $cache(function() use ($conditions) {
          return SomeModel::findAll($conditions)
        }, 600);
        

        или
        $uploader = new Http\Uploader();
        $fileName = $uploader('image');
        


        1. guyfawkes
          10.06.2015 21:21
          +8

          Возможно, дело вкуса, просто я вот за всю практику на PHP не припомню ни одного случая, когда не смог без него обойтись. Примеров, подобных вашим, тоже как-то не встречал. Вот что сделается в первом примере, что во втором? Надо лезть в код, а в случае нормально названных и вызванных методов у классов — уже код банально читается проще, можно что-то пропустить во время копаний, не отвлекаясь


          1. AlexLeonov Автор
            10.06.2015 21:26

            В первом случае кэширование делается. Есть функция, возвращающая некие данные, она передается кэшеру. Тот вернет либо значение из кэша, либо, если оно «протухло», вызовет эту функцию, положит данные в кэш и вернет их. Это гораздо удобнее, чем бесконечные get/set, вся логика кэширования инкапсулирована в объект.

            Во-втором случае объект класса Uploader загружает файл, пришедший с формы из поля 'image' и возвращает путь до удачно загруженного файла.

            Оба случая вполне оправданы, имхо. Минималистичный интерфейс объектов (наружу смотрит только __invoke) плюс полноценная инкапсуляция и наследование.


            1. guyfawkes
              10.06.2015 22:12
              +2

              Я не подразумевал своим вопросом, что именно происходит внутри ваших классов — это слабо относится к предмету разговора. Я говорил об удобстве чтения исходников.

              Помимо этого, в классы может понадобиться добавить еще пару методов.

              Наконец, чем хуже $cache->get(function() {}), почему метод, скажем, проверки доступности кеша «менее неявный», чем функционал, вызываемый __invoke?

              Не знаю. В фреймворках в качестве хорошей практики так пишут?


              1. AlexLeonov Автор
                10.06.2015 22:19
                -2

                Наконец, чем хуже $cache->get(function() {})

                Да ничем. Вон, в некоторых фреймворках бизнес-логика в роутере — и ничего, пользуются популярностью )))
                Поэтому я совершенно спокойно отношусь и к ->get() и к ->__invoke() и даже к CacheFactory::getInstance()->getDriver('memcache')->get($key)

                «Хорошие практики» в PHP еще с самом начале пути, так что ответ на ваш вопрос может дать только время. И PSR )))


                1. guyfawkes
                  10.06.2015 22:27
                  -1

                  То есть вполне невинный вызов get вы сравнили с бизнес-логикой в роутере. Ну ок.


                  1. AlexLeonov Автор
                    10.06.2015 22:34
                    +2

                    Где же тут сравнение? Сравнение — это «хуже», «лучше», «равносильно» или, скажем, «поэтичнее» или «вкуснее».
                    Я же просто посетовал на общую неустроенность PHP-тусовки, в которой попытки группы PSR навести хоть какой-то порядок выглядят очень многообещающе, но пока что явно недостаточно.
                    И специально акцентировал внимание на том, что я спокойно отношусь к любому стилю кода, потому что отчетливо понимаю, что всё это временно и рано или поздно стили также стандартизируются, как и сам язык.


                    1. guyfawkes
                      10.06.2015 22:40
                      -1

                      Явное лучше неявного? :)


                      1. AlexLeonov Автор
                        10.06.2015 22:48

                        Это всё равно что спросить «Готов к труду и обороне?»
                        Всегда готов!


                        1. guyfawkes
                          10.06.2015 22:51
                          -2

                          Тогда странно, что вам больше нравится __invoke.


                          1. AlexLeonov Автор
                            10.06.2015 22:55
                            +1

                            Вы опять приписываете мне какие-то свои домыслы. Не делайте так, пожалуйста. Я ни разу нигде не сказал, что __invoke() мне «больше нравится» (больше, чем что?)

                            Вы спросили — использую ли? Ответ — да, использую, вот примеры.
                            Вы спросили — чем же хуже просто ->get()? Я ответил — ничем.

                            Нравится ли такая возможность? Да, в целом неплохо. Но уверен, что в каждом конкретном случае код всегда может быть лучше, нет предела совершенству, особенно в таком молодом языке, как PHP.


                            1. resurection
                              12.06.2015 13:42
                              +1

                              особенно в таком молодом языке, как PHP.

                              Что???
                              Мне иногда кажется, что он старше большинства программистов пишущих на нём.


        1. seniorkrok
          16.06.2015 14:13

          Вы не могли бы дать более подробный пример кода для такого кеширования? Хочется посмотреть, как люди делают.
          Ссылку на гитхаб в идеале.


          1. AlexLeonov Автор
            16.06.2015 15:01

            То, что выше, и есть фактически рабочий пример. Ну разве что ключ забыл написать.

            Вот вам копи-паст из рабочего кода, например (с небольшими сокращениями):

                    $getBlock = function () use ($template, $route) {
                        $controller = $this->createController($route->module, $route->controller);
                        $controller->action($route->action, $route->params);
                        return $controller->view->render(
                            $route->action . (!empty($template) ? '.' . $template : '') . '.block.html',
                            $controller->getData()
                        );
                    };
            
                    if (!empty($blockOptions['cache'])) {
                        $cache = new Cache();
                        $key = md5($canonicalPath . serialize($route->params) . $template);
                        if (!empty($blockOptions['cache']['time'])) {
                            return $cache($key, $getBlock, $blockOptions['cache']['time']);
                        } else {
                            return $cache($key, $getBlock);
                        }
                    } else {
                        return $getBlock();
                    }
            


      1. to0n1
        10.06.2015 21:48

        В PHP использовал пару раз и пару раз видел в Doctrine2, но проверку типа на \Closure встречаю очень часто. В Scala приходилось использовать немного чаще. Но суть вопроса на понимание как устроены лямбды внутри.


      1. symbix
        10.06.2015 23:21

        С тех пор, как он появился, до недавнего времени использовал 1 раз, и то чтобы выпендриться. А вот недавно писал чатик на reactphp с его continuation-passing style — оказалось архиудобно таким образом оформлять actor-ы.


      1. lastbronetrain
        13.06.2015 00:45

        Zend Framework 2 View Helper


  1. SilverFire
    10.06.2015 19:15
    +3

    Для проверки, является ли переменная — функцией, которую можно вызвать, предпочитаю использовать instanceof

    $x = function () { 
        print(1); 
    };
    
    if ($x instanceof \Closure) {
        call_user_func($x);
    }
    


  1. Ekstazi
    10.06.2015 19:42

    Спасибо, жаль что не упомянули в статье про «callback» тип


    1. PQR
      10.06.2015 20:47

      Тоже читал статью и ждал такого абзаца «callable vs callback» — интересная тема, жаль упущена!


      1. AlexLeonov Автор
        10.06.2015 21:02
        +1

        Непонятно противопоставление

        callable vs callback


        Некоторые библиотечные функции принимают в качестве аргумента функции. Например array_map или usort. Десятки их. Такую функцию-аргумент другой функции и принято называть callback.

        Callback-ом может быть значение типа callable. Так что фактически это синонимы.


  1. lifehardcore
    10.06.2015 20:45

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


    1. AlexLeonov Автор
      10.06.2015 21:02

      Выше написал в ответ на аналогичный вопрос.


      1. lifehardcore
        10.06.2015 21:07
        +1

        Спасибо! Второй пример наиболее показателен и понятен.


    1. PQR
      10.06.2015 21:05
      +1

      Из реальной жизни: есть класс описывающий выпадающий список HtmlSelect. Конструктор принимает массив опций. Но иногда не хочется прям так сразу готовить этот массив опций (делать запросы к базе, например). Вместо этого передадим в анонимную функцию, которая умеет строить и возвращать массив опций. А внутри HtmlSelect::render() мы проверим, запускать ли функцию или у нас на руках готовый массив значений. Эдакий DependencyInjection для бедных — вместо целого сервиса предоставляющего список опций мы передаём либо готовые опции (допустим, примитивные [1=>'Yes', 0=>'No']), либо функцию.


      1. AlexLeonov Автор
        10.06.2015 21:09
        +1

        Вы сейчас рассказали о виджете CGridView из Yii ))
        В качестве значения ячейки таблицы можно передать или имя поля модели, или функцию, которая построит значение. Причем о последнем мануалы умалчивают и разработчики очень редко знают.


        1. PQR
          10.06.2015 21:18

          На Yii не пишу, но это действительно удобно, зря мануалы умалчивают.


        1. glagola
          10.06.2015 21:27

          Pull request, исправьте ситуацию)


          1. AlexLeonov Автор
            10.06.2015 21:50

            Смысла в первом Yii уже не вижу, второй не использую.


            1. glagola
              10.06.2015 21:59
              +1

              Yii2 весьма хорош ;)


              1. AlexLeonov Автор
                10.06.2015 22:01

                Возможно. У меня свой Yii3 есть )))


                1. glagola
                  10.06.2015 22:21

                  своя разработка?


                  1. AlexLeonov Автор
                    10.06.2015 22:21

                    Коллективная. Отчасти моя, отчасти «группы товарищей».


                    1. glagola
                      10.06.2015 22:52

                      В публичном доступе? ссылочкой поделитесь?


                      1. AlexLeonov Автор
                        10.06.2015 22:55

                        Ждал этого вопроса. В личку поделюсь. К массовой публичности не готов.


                1. Mistx
                  11.06.2015 09:58

                  Теряется смысл фреймворка, если писать свой. Намного больше преимуществ даёт использование одного из популярных + своей библиотеки компонентов к нему


                  1. AlexLeonov Автор
                    11.06.2015 11:32

                    Расскажите это Фабьену или Taylor Otwell


        1. seregagl
          11.06.2015 11:59

          А мануалы не умалчивают. В них написано, что:

          string a PHP expression that will be evaluated for every data cell using {@link evaluateExpression}

          В свою очередь в помощи к evaluateExpression указано

          Evaluates a PHP expression or callback under the context of this component


          1. AlexLeonov Автор
            11.06.2015 13:09

            Да, но официально первый Yii работает на PHP <=5.2
            Следовательно в нем самом нигде анонимные функции не применяются. Более того, в мануале рекомендуется жёсткий eval() в таких местах.

            Хотя о чем мы, первый Yii уже нет смысла обсуждать в 2015 году…


  1. maximw
    10.06.2015 23:02
    +5

    Немного дополню. Если в классе объявлены магические __call или __callStatic, то проверка любого метода is_callable будет true.

    <?php
    class a {
    	public function __call($foo, $arguments) {}
    	public static function __callStatic($foo, $arguments) {}
    }
    $obj = new a();
    var_dump(is_callable('a::bar')); // bool(true)
    var_dump(is_callable([$obj, 'baz'])); // bool(true)
    


    1. AlexLeonov Автор
      10.06.2015 23:03

      Совершенно верно. Это я как-то упустил, надо бы добавить в статью. Спасибо.


  1. Athari
    10.06.2015 23:07

    Вообще-то за вычетом абсолютно бесполезной формы "Baz::foo" всё остальное callable работает безо всяких call_user_func:

    class Baz
    {
        static function foo () { return 'foo'; }
        function bar () { return 'bar'; }
        function __invoke() { return 'baz'; }
    }
    function foo () { return 'foo'; }
    
    $foo = new Baz;
    
    $f = 'foo';
    assert($f() == 'foo');
    
    $f = [ Baz::class, 'foo' ];
    assert($f() == 'foo');
    
    $f = [ 'Baz', 'foo' ];
    assert($f() == 'foo');
    
    $f = [ $foo, 'bar' ];
    assert($f() == 'bar');
    
    $f = $foo;
    assert($f() == 'baz');
    
    // Fatal error: Call to undefined function Baz::foo()
    //$f = 'Baz::foo';
    //assert($f() == 'foo');

    Хотя, может, уже и это починили, потому что у меня похапэ старый стоит.


    1. to0n1
      10.06.2015 23:25

      Очень часто встречаю во всяких event disaptcher'ах регистрацию слушателей как [$this, 'methodName']
      Так вот это работать и не будет в вашем примере. PHP 5.6.6

      код
      <?php
      
      class a{
          function b () {
              echo 'b';
          }
      }
      
      $a = new a();
      [$a, 'b']();
      


      1. symbix
        11.06.2015 00:24
        +1

        В 5.6 — так не будет, через временную переменную — будет. А в php7 вообще все хорошо — реализация AST исправила все подобные неконсистентности, работает и ваш пример, и любимая JS-разработчиками форма записи (function(){})();, и даже ($this->someCallableProperty)().


        1. Athari
          11.06.2015 02:14

          А (function(){return[new Exception,'rtrim'('ltrim ')(' getTrace')]()['file'];})()[0] будет работать? :) Особенно строки интересуют, потому что синтаксис получается забавный.


          1. bolk
            11.06.2015 07:21

            Да, в такой форме работает.


          1. symbix
            11.06.2015 17:00

            Ага, работает.

            Вообще, во всех случаях, когда в PHP <7 приходилось заводить временную переменную, в PHP7 должно работать напрямую. Главное — правильно скобочки не забыть расставить в тех случаях, когда без скобочек такая запись означает что-то иное.


      1. Athari
        11.06.2015 00:25

        Это проблема исключительно на уровне синтаксического анализа, во время выполнения кода всё прекрасно работает. Просто комбинация скобок []() в костыль-дривен парсере не поддерживается. Вот так работать будет:

        $f = [$a, 'b'];
        $f();

        Так как после «регистрации слушателей во всяких event dispatcher'ах» хранятся переменные, то проблемы не существует.

        Можно попробовать в PHP 7. Вроде, там обещали человеческий парсер.

        К слову, схожая ситуация при вызове колбэков из свойств: в языке не существует синтаксис ($this->a)(), тоже приходится писать в две строчки (в PHP 5.5, по крайней мере).

        [ UPD: symbix А, ну сверху уже написали, что теперь оба случая нормально работают. ]

        P. S. call_user_func и особенно call_user_func_array жутко тормозные, поэтому в новом коде их следует избегать по возможности.


        1. symbix
          11.06.2015 23:20

          В новом коде их следует избегать всегда, если не нужна совместимость с PHP <7 — хотя разница не такая и большая:

          $ time ./php -r 'class X{function f($a, $b){}}; $x = new X(); for ($i=0; $i<1000000; ++$i) call_user_func_array([$x, 'f'], [1,2]);'

          real 0m1.037s
          user 0m1.024s
          sys 0m0.012s

          $ time ./php -r 'class X{function f($a, $b){}}; $x = new X(); for ($i=0; $i<1000000; ++$i) [$x, 'f'](...[1,2]);'

          real 0m0.901s
          user 0m0.885s
          sys 0m0.012s


          1. Athari
            11.06.2015 23:27

            О, разворачивалка массива в аргументы тоже появилась…

            Фраза «если не нужна совместимость с PHP <7» несколько забавно звучит, учитывая, что до релиза ещё дожить надо. :)


            1. symbix
              12.06.2015 01:47

              Ну почему, новые проекты, достаточно крупные, чтобы не быть готовыми к продакшену до выхода php7, уже можно на нем начинать разрабатывать вполне.


  1. Acuna
    10.06.2015 23:28

    >> невозможно вызвать ее напрямую, с помощью $x(), мы получим ошибку вида «Fatal error: Call to undefined function Foo::bar()» И здесь нам на помощь приходит специальная функция call_user_func(), которая умеет обходить «острые углы» и вызывать значения псевдотипа callable, даже если это невозможно с помощью обычного синтаксиса.

    Кому-нибудь известно, это баг или фича такая?


    1. bolk
      11.06.2015 07:23

      Особенность парсера языка. В следующей мажорной версии этих и подобных особенностей не будет.


      1. Acuna
        12.06.2015 02:24

        Ясно, баг значит, так я и думал)


        1. VolCh
          12.06.2015 15:06

          Нельзя сказать, что баг. Не смогли сделать универсально и объявили особенностью :)


          1. Acuna
            13.06.2015 02:50

            Ахах, вот и правильно, я теперь тоже думаю, что это «особенность» такая, а никакой не баг)))


  1. bolk
    11.06.2015 07:15

    Дело в том, что на самом деле create_function() не создавала лямбда-функцию в современном понимании, фактически эта функция создавала именованную функцию с именем наподобие «lambda_1» и возвращала ее имя.
    Не совсем так. Чтобы эти имена не пересекались с создаваемыми через обычный function, авторы языка первым символом поставили символ с кодом ноль:

    var_dump(ord(create_function("",""))); // 0