PHP 5.3 вызов магического метода
Эта тема расширяется на Когда я должен использовать _ _ construct (), __get (), __set () и _ _ call () в PHP? который говорит о __construct
, __get
и __set
магические методы.
Начиная с PHP 5.3 существует новый магический метод под названием __invoke
. Метод __invoke
вызывается, когда скрипт пытается вызвать объект как функцию.
Теперь, когда я провел исследование этого метода, люди сравнивают его с методом Java .run()
- см. интерфейс Runnable.
Подумав долго и я не могу придумать ни одной причины, по которой вы назвали бы $obj();
, а не $obj->function();
Даже если бы вы перебирали массив объектов, вы все равно знали бы имя главной функции, которую вы хотите запустить.
Так является ли волшебный метод __invoke
Еще одним примером ярлыка "только потому, что вы можете, не означает, что вы должны" в PHP, или есть случаи, когда это действительно было бы правильным?
6 ответов:
PHP не позволяет передавать указатели функций, как другие языки. Функции не являются первым классом в PHP. Функции первого класса в основном означают, что вы можете сохранить функцию в переменной, передать ее и выполнить в любое время.
Метод
__invoke
-это способ, которым PHP может использовать псевдо-первоклассные функции.Метод
__invoke
может использоваться для передачи класса, который может действовать как замыкание или продолжение, или просто как функция, которую вы можете передать по кругу.Многие функциональные программы опираются на функции первого класса. Даже нормальное императивное программирование может извлечь из этого пользу.
Предположим, у вас была процедура сортировки, но вы хотели поддерживать различные функции сравнения. Ну, у вас могут быть разные классы сравнения, которые реализуют функцию _ _ invoke и передают экземпляры в класс вашей функции сортировки, и ей даже не нужно знать имя функции.
Действительно, Вы всегда можете сделали что-то вроде передачи класса, и функция вызывает метод, но теперь вы можете почти говорить о передаче "функции" вместо передачи класса, хотя это не так чисто, как в других языках.
Использование
__invoke
имеет смысл, когда вам нужен вызываемый , который должен поддерживать некоторое внутреннее состояние. Допустим, вы хотите отсортировать следующий массив:$arr = [ ['key' => 3, 'value' => 10, 'weight' => 100], ['key' => 5, 'value' => 10, 'weight' => 50], ['key' => 2, 'value' => 3, 'weight' => 0], ['key' => 4, 'value' => 2, 'weight' => 400], ['key' => 1, 'value' => 9, 'weight' => 150] ];
Функцияusort позволяет сортировать массив с помощью некоторой функции, очень простой. Однако в этом случае мы хотим отсортировать массив, используя его внутренний ключ arrays
'value'
, что можно сделать следующим образом:$comparisonFn = function($a, $b) { return $a['value'] < $b['value'] ? -1 : ($a['value'] > $b['value'] ? 1 : 0); }; usort($arr, $comparisonFn); // ['key' => 'w', 'value' => 2] will be the first element, // ['key' => 'w', 'value' => 3] will be the second, etc
Теперь, возможно, вам нужно снова отсортировать массив, но на этот раз используя
'key'
в качестве целевой ключ, необходимо было бы переписать функцию:Как вы видите, логика функции идентична предыдущей, однако мы не можем повторно использовать предыдущую из-за необходимости сортировки с другим ключом. Эта проблема может быть решена с помощью класса, который инкапсулирует логику сравнения в методеusort($arr, function($a, $b) { return $a['key'] < $b['key'] ? -1 : ($a['key'] > $b['key'] ? 1 : 0); });
__invoke
и определяет ключ, который будет использоваться в его конструкторе:class Comparator { protected $key; public function __construct($key) { $this->key = $key; } public function __invoke($a, $b) { return $a[$this->key] < $b[$this->key] ? -1 : ($a[$this->key] > $b[$this->key] ? 1 : 0); } }
Объект класса, реализующий
__invoke
это "вызываемый", он может быть использован в любой контекст, которым может быть функция, поэтому теперь мы можем просто создать экземплярComparator
объектов и передать их функцииusort
:usort($arr, new Comparator('key')); // sort by 'key' usort($arr, new Comparator('value')); // sort by 'value' usort($arr, new Comparator('weight')); // sort by 'weight'
Следующие абзацы отражают мое субъективное мнение, поэтому, если вы хотите, вы можете прекратить читать ответ Сейчас ;): хотя предыдущий пример показал очень интересное использование
__invoke
, такие случаи редки, и я бы избегал его использования, так как это может быть сделано действительно запутанными способами и, как правило, есть более простые альтернативы реализации. Один примером альтернативы в той же задаче сортировки может служить использование функции, возвращающей функцию сравнения:function getComparisonByKeyFn($key) { return function($a, $b) use ($key) { return $a[$key] < $b[$key] ? -1 : ($a[$key] > $b[$key] ? 1 : 0); }; } usort($arr, getComparisonByKeyFn('weight')); usort($arr, getComparisonByKeyFn('key')); usort($arr, getComparisonByKeyFn('value'));
Хотя этот пример требует немного большей близости с lambdas | closures | anonymous функциями, он гораздо более лаконичен, поскольку он не создает целую структуру класса только для хранения внешнего значения.
Я считаю, что эта функциональность существует главным образом для поддержки новой функциональности закрытия 5.3. Замыкания представляются как экземпляры класса
Closure
и являются непосредственно вызываемыми, например$foo = $someClosure();
. Практическим преимуществом__invoke()
является то, что становится возможным создать стандартный тип обратного вызова, а не использовать странные комбинации строк, объектов и массивов в зависимости от того, ссылаетесь ли вы на функцию, метод экземпляра или статический метод.
Это комбинация двух вещей. Вы уже правильно определили одного из них. Это действительно похоже на интерфейс Java
IRunnable
, где каждый "запускаемый" объект реализует один и тот же метод. В Java метод называетсяrun
; в PHP метод называется__invoke
, и вам не нужно явно реализовывать какой-либо конкретный тип интерфейса заранее.Второй аспект-это синтаксический сахар , поэтому вместо вызова
$obj->__invoke()
Вы можете пропустить имя метода, поэтому он выглядит так, как будто вы вызываете объект напрямую:$obj()
.Ключевая часть для PHP, чтобы иметь замыкания, является первой. Язык нуждается в некотором установленном методе для вызова объекта закрытия, чтобы заставить его делать свое дело. Синтаксический сахар-это просто способ сделать его менее уродливым, как и в случае со всеми "специальными" функциями с префиксами двойного подчеркивания.
На самом деле вы не должны вызывать
$obj();
в отличие от$obj->function();
, Если вы знаете, что имеете дело с определенным типом объекта. Тем не менее, если вы не хотите, чтобы ваши коллеги почесали свои головы.Метод
Представьте, что у вас есть метод в классе (который вы должны использовать и не можете изменить), который принимает только вызываемый аргумент.__invoke
оживает в различных ситуациях. Особенно, когда вы должны предоставить универсальный вызываемый объект в качестве аргумента.$obj->setProcessor(function ($arg) { // do something costly with the arguments });
А теперь представьте вы хотите кэшировать и повторно использовать результат длительной операции или получить доступ к ранее использованным аргументам этой функции. С регулярными закрытиями, которые могут быть коренастыми.
Видите ли, если вам понадобится получить доступ к// say what? what is it for? $argList = []; $obj->setProcessor(function ($arg) use (&$argList) { static $cache; // check if there is a cached result... // do something costly with the arguments // remember used arguments $argList[] = $arg; // save result to a cache return $cache[$arg] = $result; });
$argList
откуда-то еще или просто очистить кэш от заблокированных записей, вы в беде!Здесь приходит
__invoke
на помощь:class CachableSpecificWorker { private $cache = []; private $argList = []; public function __invoke($arg) { // check if there is a cached result... // remember used arguments $this->argList[] = $arg; // do something costly with the arguments // save result to a cache return $this->cache[$arg] = $result; } public function removeFromCache($arg) { // purge an outdated result from the cache unset($this->cache[$arg]); } public function countArgs() { // do the counting return $resultOfCounting; } }
С классом выше работа с кэшированными данными становится легким делом.
$worker = new CachableSpecificWorker(); // from the POV of $obj our $worker looks like a regular closure $obj->setProcessor($worker); // hey ho! we have a new data for this argument $worker->removeFromCache($argWithNewData); // pass it on somewhere else for future use $logger->gatherStatsLater($worker);
Это всего лишь простой пример, чтобы проиллюстрируйте концепцию. Можно пойти еще дальше и создать универсальный класс оболочки и кэширования. И многое другое.
Заключение (на основе всего вышесказанного)
Вообще я вижу _ _ invoke(){...} магический метод как отличная возможность для абстрагирования использования основной функциональности объекта класса или для интуитивной настройки объекта (подготовка объекта перед использованием его методов).
Случай 1-Например, я использую какой-то сторонний объект, реализующий метод __invoke magic, обеспечивающий таким образом легкий доступ к основной функциональности экземпляра объекта. Чтобы использовать его главную особенность мне нужно только знать, что параметры _ _ invoke метод ожидает и что будет конечным результатом этой функции (закрытия). Таким образом, я могу использовать основную функциональность объекта класса с небольшими усилиями, чтобы вложить возможности объекта (обратите внимание, что в этом примере нам не нужно знать или использовать какое-либо имя метода).
Абстрагирование от реального кода...
Вместо
$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2);
Теперь мы используем:
$obj($arg1, $arg2);
Теперь мы можем также передать объект другим функциям, которые ожидают, что его параметры будут вызывается так же, как и в обычной функции:
Вместо
someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), 'someFunctionNameInitTheMainFunctionality']);
Теперь мы используем:
someFunctionThatExpectOneCallableArgument($someData, $obj);
__invoke также предоставляет хороший ярлык использования, так почему бы не использовать его ?