Что такое callable?

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

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

Да легко. Самый часто используемый в современном языке вариант callable — это анонимная функция. ( https://3v4l.org/7EB6p )

$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. Когда вы создаете новую анонимную функцию, по сути вы неявно создаете объект этого класса. Подробнее об этом можно прочитать в мануале (2)

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

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

Строки в PHP вполне могут быть callable! В этом случае интерпретатор будет искать обычную, неанонимную функцию с именем, совпадающим с данной строкой и, в случае успеха, вызовет такую функцию.

function foo($bar) {
  return $bar * 2;
}

$x = 'foo';

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

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

( см. https://3v4l.org/A3VvYX — true в обоих случаях )

Таким образом можно вызывать как свои функции, так и библиотечные. Есть ряд ограничений — нельзя вызвать 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) 
);

( см. https://3v4l.org/cC9g8 — true в обоих случаях )

Вторая особенность такой 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(). В первую очередь знать такие вещи нужно для собственного самосовершенствования, чтобы понимать все плюсы и минусы разных подходов, конструкций и парадигм, и уметь выбрать нужные в нужный момент времени.


Источники:

(1) https://habr.com/ru/post/259991/

(2) http://php.net/manual/ru/class.closure.php

Tags

Нет комментариев

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.