PHP 5.3 вызов магического метода

эта тема расширяется на когда / должен ли я использовать _ _ construct (), __get (), __set () и __call () в PHP? который говорит о __construct, __get и __set магические методы.

начиная с PHP 5.3 существует новый магический метод под названием __invoke. The __invoke метод вызывается, когда скрипт пытается вызвать объект как функцию.

теперь при исследовании, которое я сделал для этого метода, люди сравнивают его с методом Java .run() - см. интерфейс Runnable.

подумав долго и упорно об этом, я не могу придумать ни одной причины, по которой вы бы позвонили $obj(); в противоположность $obj->function();

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

так __invoke magic method еще один пример "просто потому, что вы можете, не означает, что вы должны" ярлык в PHP, или есть случаи, когда это действительно было бы правильно делать?

6 ответов


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

на __invoke метод-это способ, которым PHP может размещать псевдо-первоклассные функции.

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

много функциональное программирование полагается на функциях первого класса. Даже нормальное императивное программирование может извлечь из этого пользу.

скажем, у вас была процедура сортировки, но вы хотели поддерживать разные функции сравнения. Ну, у вас могут быть разные классы сравнения, которые реализуют функцию _ _ 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 функция позволяет сортировать массив с помощью некоторой функции, очень простой. Однако в этом случае мы хотим отсортировать массив, используя его внутренние массивы '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);
    }
}

A Объект класса, реализующий __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 / закрытие / анонимные функции это гораздо более лаконично, так как он не создает целую структуру классов только для хранения внешнего значения.


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

Случай 1-например, скажем, что я использую сторонний объект, который реализует _ _ invoke magic метод, обеспечивающий таким образом легкий доступ к основным функциям экземпляра объекта. Чтобы использовать его, мне нужно только знайте, какие параметры _ _ invoke ожидает метод и каким будет конечный результат этой функции (закрытия). Таким образом, я могу использовать основную функциональность объекта класса только с небольшими усилиями, чтобы инвестировать возможности объекта (обратите внимание, что в этом примере нам не нужно знать или использовать какое-либо имя метода).

абстрагирование от реального кода...

вместо

$obj->someFunctionNameInitTheMainFunctionality($arg1, $arg2);

мы используем:

$obj($arg1, $arg2);

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

вместо

someFunctionThatExpectOneCallableArgument($someData, [get_class($obj), 'someFunctionNameInitTheMainFunctionality']);

мы используем:

someFunctionThatExpectOneCallableArgument($someData, $obj);

__invoke также обеспечивает хороший ярлык использования, так почему бы не использовать его ?