PHP 5.3 Magic Method invoke

Ten temat rozszerza się na Kiedy powinienem używać _ _ construct (), __get (), __set () I __call () w PHP? który mówi o __construct, __get i __set magiczne metody.

Od wersji PHP 5.3 istnieje Nowa Magiczna Metoda o nazwie __invoke. Metoda __invoke jest wywoływana, gdy skrypt próbuje wywołać obiekt jako funkcję.

Teraz w badaniach, które zrobiłem dla tej metody, ludzie porównują ją do metody Java .run() - patrz Interface Runnable.

Myśląc długo i trudno o to nie mogę wymyślić żadnego powodu, dla którego zadzwoniłbyś $obj(); W przeciwieństwie do $obj->function();

Nawet gdybyś iterował nad tablicą obiektów, nadal znałbyś główną nazwę funkcji, którą chcesz uruchomić.

Więc czy metoda magiczna __invoke jest kolejnym przykładem skrótu 'tylko dlatego, że możesz, nie znaczy, że powinieneś' w PHP, czy są przypadki, w których byłoby to właściwe?

Author: Community, 2009-05-20

6 answers

PHP nie pozwala na przekazywanie wskaźników funkcji, tak jak inne języki. Funkcje nie są pierwszą klasą w PHP. Funkcja pierwsza klasa oznacza przede wszystkim, że można zapisać funkcję do zmiennej, a następnie przekazać ją i wykonać w dowolnym momencie.

Metoda __invoke jest sposobem, w jaki PHP może pomieścić funkcje pseudo-pierwszej klasy.

Metoda __invoke może być użyta do przejścia klasy, która może działać jako zamknięcie lub kontynuacja , lub po prostu jako funkcja, którą możesz przekazać.

Wiele funkcji programowania opiera się na funkcjach pierwszej klasy. Nawet normalne programowanie imperatywne może z tego skorzystać.

Powiedzmy, że miałeś rutynę sortowania, ale chciałeś obsługiwać różne funkcje porównywania. Cóż, możesz mieć różne klasy porównujące, które implementują funkcję _ _ invoke i przechodzą w instancjach do klasy do swojej funkcji sortowania, a nawet nie musi znać nazwy funkcji.

Naprawdę, zawsze możesz zrobiłeś coś takiego jak przekazywanie klasy i wywołanie funkcji metodą, ale teraz możesz prawie mówić o przekazaniu "funkcji" zamiast przekazywania klasy, chociaż nie jest to tak czyste, jak w innych językach.
 24
Author: Kekoa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-20 14:16:15

Zastosowanie __invoke ma to sens, gdy potrzebujesz wywoływalnego, który musi utrzymać jakiś stan wewnętrzny. Powiedzmy, że chcesz posortować następującą tablicę:

$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]
];

Funkcja usort pozwala na sortowanie tablicy za pomocą jakiejś funkcji, bardzo prostej. Jednak w tym przypadku chcemy posortować tablicę używając jej wewnętrznego klucza 'value', co można zrobić w ten sposób:

$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

Teraz może trzeba posortować tablicę jeszcze raz, ale tym razem używając 'key' jako klucz docelowy, konieczne byłoby przepisanie funkcji:

usort($arr, function($a, $b) {
    return $a['key'] < $b['key'] ? -1 : ($a['key'] > $b['key'] ? 1 : 0);
});

Jak widać logika funkcji jest identyczna z poprzednią, jednak nie możemy ponownie użyć poprzedniej ze względu na konieczność sortowania za pomocą innego klucza. Problem ten można rozwiązać za pomocą klasy, która zawiera logikę porównania w metodzie __invoke i która definiuje klucz, który ma być użyty w jej konstruktorze:

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);
    }
}

Obiekt klasy, który implementuje __invoke jest "wywoływalny", może być użyty w każdy kontekst, w którym może być funkcja, więc teraz możemy po prostu utworzyć instancję Comparator obiektów i przekazać je dousort funkcji:

usort($arr, new Comparator('key')); // sort by 'key'

usort($arr, new Comparator('value')); // sort by 'value'

usort($arr, new Comparator('weight')); // sort by 'weight'

Poniższe akapity odzwierciedlają moją subiektywną opinię, więc jeśli chcesz, możesz przestać czytać odpowiedź teraz;) : chociaż poprzedni przykład pokazał bardzo ciekawe użycie __invoke, takie przypadki są rzadkie i unikałbym jego użycia, ponieważ można to zrobić w naprawdę mylące sposoby i ogólnie istnieją prostsze alternatywy implementacji. Na przykładem alternatywy w tym samym problemie sortowania byłoby użycie funkcji, która zwraca funkcję porównawczą:

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'));

Chociaż ten przykład wymaga trochę więcej intymności z lambdas | closures | anonymous functions jest o wiele bardziej zwięzły, ponieważ nie tworzy całej struktury klas tylko po to, aby przechowywać zewnętrzną wartość.

 16
Author: BrunoRB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 18:58:27

Uważam, że ta funkcjonalność istnieje głównie po to, aby wspierać nową funkcjonalność zamknięcia 5.3. Zamknięcia są eksponowane jako instancje klasy Closure i są bezpośrednio wywoływane, np. $foo = $someClosure();. Praktyczną zaletą __invoke() jest to, że możliwe staje się utworzenie standardowego typu wywołania zwrotnego, a nie używanie dziwnych kombinacji łańcuchów, obiektów i tablic w zależności od tego, czy odwołujesz się do funkcji, metody instancji czy metody statycznej.

 14
Author: jaz303,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-20 14:15:22

To połączenie dwóch rzeczy. Zidentyfikowałeś już jednego z nich. Jest to tak samo jak interfejs IRunnable Javy, gdzie każdy obiekt" runnable " implementuje tę samą metodę. W języku Java metoda ma nazwę run; w PHP metoda ma nazwę __invoke i nie trzeba wcześniej jawnie implementować żadnego konkretnego typu interfejsu.

Drugim aspektem jest składniowy cukier , więc zamiast wywoływać $obj->__invoke(), możesz pominąć nazwę metody, więc wygląda to tak, jakby wywołujesz obiekt bezpośrednio: $obj().

Kluczową częścią dla PHP jest pierwsza. Język potrzebuje jakiejś ustalonej metody, aby wywołać obiekt zamknięcia, aby zrobił swoje. Cukier składniowy jest tylko sposobem na to, aby wyglądał mniej brzydko, jak ma to miejsce w przypadku wszystkich funkcji "specjalnych" z przedrostkami podwójnego podkreślenia.

 3
Author: Rob Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-20 14:24:35

Naprawdę nie powinieneś dzwonić $obj(); w przeciwieństwie do $obj->function();, jeśli wiesz, że masz do czynienia z określonym typem obiektu. To powiedziawszy, chyba że chcesz, aby Twoi koledzy podrapali się po głowach.

Metoda __invoke ożywa w różnych sytuacjach. Zwłaszcza, gdy oczekuje się, że jako argument podasz ogólne wywołanie.

Wyobraź sobie, że masz metodę w klasie (której musisz użyć i nie możesz zmienić), która przyjmuje tylko wywołanie jako argument.

$obj->setProcessor(function ($arg) {
    // do something costly with the arguments
});

Teraz wyobraź sobie chcesz buforować i ponownie wykorzystać wynik długotrwałej operacji lub uzyskać dostęp do wcześniej użytych argumentów do tej funkcji. Z regularnymi zamknięciami, które mogą być grube.

// 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;
});

Widzisz, jeśli zdarzy ci się, że potrzebujesz dostępu do $argList z innego miejsca, lub po prostu wyczyść pamięć podręczną zablokowanych wpisów, jesteś w tarapatach!

Nadchodzi __invoke na ratunek:

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;
    }
}

Z powyższą klasą praca z buforowanymi danymi staje się bardzo prosta.

$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);

To tylko prosty przykład do zilustruj koncepcję. Można pójść jeszcze dalej i utworzyć ogólny wrapper i klasę buforowania. I wiele więcej.

 2
Author: sanmai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-12 03:07:14

Podsumowanie (na podstawie wszystkich powyższych)

Ogólnie widzę _ _ invoke () {...} metoda magiczna jako doskonała okazja do abstrakcyjnego wykorzystania głównej funkcjonalności obiektu klasy lub do intuicyjnego ustawienia obiektu(przygotowanie obiektu przed użyciem jego metod).

Przypadek 1-powiedzmy na przykład, że używam jakiegoś zewnętrznego obiektu, który implementuje magiczną metodę _ _ invoke, zapewniając w ten sposób łatwy dostęp do głównej funkcjonalności instancji obiektu. Aby z niego korzystać, muszę tylko wiedzieć, co parameters _ _ invoke metoda oczekuje i jaki byłby wynik końcowy tej funkcji (zamknięcie). W ten sposób jestem w stanie korzystać z głównej funkcjonalności obiektu klasy przy niewielkim wysiłku, aby zainwestować w możliwości obiektu (zauważ, że w tym przykładzie nie musimy znać ani używać żadnej nazwy metody).

Abstrakcja z prawdziwego kodu...

Zamiast

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

Używamy teraz:

$obj($arg1, $arg2);

Możemy teraz przekazać obiekt innym funkcjom, które oczekują, że jego parametry będą wywoływalne tak jak w funkcji regularnej:

Zamiast

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

Używamy teraz:

someFunctionThatExpectOneCallableArgument($someData, $obj);

_ _ invoke zapewnia również ładny Skrót użytkowania, więc dlaczego nie używać go ?

 1
Author: DevWL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-08 20:43:10