środa, 27 maja 2015

[PHP] Funkcje anonimowe i callback

TRUE
2147216564926567225
Funkcje anonimowe i związane z nimi funkcje zwrotne (tzw. callback) to bardzo praktyczny mechanizm pozwalający na tworzenie bardzo ciekawych rozwiązań. Są wykorzystywane w wielu funkcjach wbudowanych w PHP, możemy też je wykorzystywać do budowania własnych tego typu rozwiązań. Trzeba bowiem wiedzieć, że jako argumenty do danej funkcji możemy w PHP przekazywać nie tylko zwykłe typy takie jak obiekty klas, zmienne, czy tablice, ale również i funkcje.

Co więcej - funkcje podane jako argument mogą operować na danych zwróconych przez funkcję wywoływaną - nazywamy je wtedy funkcjami zwrotnymi, tzw. callbackami. Oczywiście nic nie stoi na przeszkodzie, aby wszystko działało o wiele bardziej skomplikowanie - funkcja zwrotna może np. odbierać wynik, przetwarzać go, a następnie ponownie przekazywać do funkcji bazowej. W tym artykule dowiemy się w jaki sposób w PHP można deklarować tego typu funkcje oraz zobaczymy na przykładzie w jaki praktyczny sposób można to rozwiązanie wykorzystać.

function()

Funkcja anonimowa może zostać zadeklarowana na dwa sposoby, na początek poznamy pierwszy sposób. Nie musi ona posiadać swojej nazwy, można ją jednak przypisać do zmiennej i używać zmiennej tak jakby była zwykłą funkcją.
Składnia taka moze powodować zdziwienie na pierwszy rzut oka, ale po zrozumieniu mechanizmu nie będzie w niej dla nas niczego dziwnego. Zdeklarujmy więc naszą pierwszą anonimową funkcję, przypisując ją do zmiennej:
[code]
<?php
$myFunction = function() {
  echo 'This is myFunction.<br />';
};
?>
[/code]

Jak widać powyżej, funkcja taka nie posiada własnej nazwy, jest za to przypisywana do zmiennej $myFunction.
Co za tym idzie, wywołajmy teraz taki kod:
[code]$myFunction(); [/code]
Dziwna konstrukcja? Na pierwszy rzut oka być może tak, bo wygląda jak połączenie zmiennej z funkcją.
Tak też jest w istocie. Wywołanie powyższego nie spowoduje żadnego błędu i wyświetli nam wynik działania funkcji zadeklarowanej wyżej, czyli:
[code]This is myFunction.[/code]

Wyobraźmy sobie teraz, że mamy dodatkową funkcję przyjmującą jeden argument, np.:
[code]
function otherFunction($arg)
{
  // ....ciało funkcji
}
[/code]
Co zostanie do niej przekazane, gdy wywołamy ją podając za argument zmienną $myFunction?
[code]otherFunction($myFunction);[/code]
Otóż - przekazana w argumencie zostanie nasza anonimowa funkcja.
Funkcję przekazaną możemy następnie wywołać w ciele funkcji do której została przekazana i zrobić z nią co tylko chcemy.

Funkcje anonimowe mogą naturalnie przyjmować argumenty, rozbudujmy zatem naszą anonimową funkcję o pobieranie dwóch liczbowych argumentów, następnie w wyniku wyświetlanie ich sumy:
[code]
$myFunction = function($a, $b) {
  $c = $a + b;
  echo 'This is myFunction:<br />' .
  $a . '+' . $b . '=' . $c . '<br />';
};
[/code]

Wywołanie teraz:
[code]$myFunction(5, 2);[/code]
Spowoduje wywołanie funkcji i wyświetlenie nam:
[code]This is myFunction:
5+2=7[/code]

call_user_func()

Funkcje anonimowe (jak i zwykłe funkcje) wywoływać można za pomocą funkcji:
[code]call_user_func(nazwa_funkcji, argument1, argument2, ....);[/code]

Przykładowo:
[code]call_user_func($myFunction, 5, 2);[/code]
Wywoła nam funkcję anonimową przypisaną do zmiennej $myFunction z argumentami 5 i 2, co finalnie wyświetli nam wynik tak jak powyżej. W przykładzie tym podaliśmy zmienną zamiast nazwy funkcji, zwykłe wywołanie za pomocą podania jej nazwy jako stringu wyglądało by np. tak:
[code]
$html = '<b>this is html</b>';
$no_tags = call_user_func('strip_tags', $html);
echo $no_tags;
[/code]
Co w wyniku wywoła nam funkcję strip_tags() z argumentem $html.

call_user_func_array()

Argumenty do call_user_func() można podawać również w formie tablicy, ale zrobimy to już funkcją:
[code]call_user_func_array(nazwa_funkcji, tablica_z_argumentami)[/code]
Przykład:
[code]
$str = 'color is black';
$a = 'black';
$b = 'white';
$replace = call_user_func_array('str_replace', array($a, $b, $str));
echo $replace;
[/code]
Wyświetli nam:
[code]color is white[/code]

call_user_func i klasy

Przeanalizowaliśmy prosty przykład z funkcjami, jak jednak za pomocą call_user_func() wywołać metody klasy.
Rozwiązań jest kilka, utwórzmy najpierw przykładową klasę:
[code]
class MyClass
{
  public function myMethod()
  {
    echo 'This is myMethod from object. <br />';  
  }

  public static function myStaticMethod()
  {
    echo 'This is myStaticMethod. <br />';      
  }
}
[/code]
Aby wywołać statyczną metodę myStaticMethod(), użyć musimy jednej z dwóch konstrukcji:
[code]call_user_func('MyClass', 'myStaticMethod');[/code]
lub bezpośrednio:
[code]call_user_func('MyClass::myStaticMethod');[/code]

W przypadku obiektu klasy i metody myMethod() zrobimy to tak:
[code]$obj = new MyClass;
call_user_func($obj, 'myMethod');[/code]
Możemy też odwoływać się np. do metody rodzica w klasie bazowej.
Utwórzmy dwie klasy, z czego druga dziedziczy po pierwszej:
[code]
class MyClassA
{  
  public static function who()
  {
    echo 'My class is: ' . __CLASS__ . '<br />';      
  }
}

class MyClassB extends MyClassA
{  
  public static function who()
  {
    echo 'My class is: ' . __CLASS__ . '<br />';      
  }
}
[/code]
Aby teraz z poziomu klasy B wywołać metodę klasy bazowej A użyjemy konstrukcji:
[code]call_user_func(array('MyClassB', 'parent::who'));[/code]
co wyświetli nam:
[code]My class is: MyClassA[/code]

CALLBACK

Wiemy już w jaki sposób wywoływać funkcje anonimowe (i zwykłe), więc zobaczmy teraz jak wykorzystać tą wiedzę do stworzenia callbacka. Na początek wymyślmy sobie jakąś funkcję, która będzie przyjmować funkcję anonimową jako callback i zwracać jej wynik swojego działania. Niech będzie to przykładowo funkcja obliczający pierwiastek kwadratowy z podanej jako argument liczby.
Funkcja obliczająca pierwiastek kwadratowy to:
[code]sqrt()[/code]
My jednak chcemy stworzyć bardziej rozbudowaną wersję, która nie tylko zwróci nam wynik, ale przekaże go do zdefiniowanej przez nas funkcji, którą podamy jako parametr. Na początek obmyślmy co taka funkcja zrobi z takim wynikiem - niech będzie, że po prostu go wyświetli.
Nasza funkcja callbackowa wyglądać więc będzie tak:
[code]
function myCallback($input)
{
  echo 'My result is: ' . $input . '<br />';
}
[/code]
Jak widać jest to prosta funkcja - przyjmuje wynik w argumencie i wyświetla go za pomocą echo().
Argument $input zostanie jej przekazany przez naszą funkcję bazową, w której obliczymy pierwiastek, stwórzmy więc i tą funkcję:
[code]
function sqrtWithCallback($number, callable $callback)
{
  $res = sqrt($number);
  if(is_callable($callback))
  {
    call_user_func($callback, $res);
  }
}
[/code]
Jak widzimy funkcja przyjmuje 2 argumenty:

  • $number - który pobiera liczbę, z której obliczać będziemy pierwiastek
  • $callback - funkcję zwrotną, której przekaże obliczony pierwiastek.

Co się dzieje dalej?
Pierwiastek jest obliczany i zapisywany w zmiennej $res.
Następnie mamy warunek, który wymaga omówienia:
Funkcja:
[code]is_callable(nazwa_funkcji)[/code]
sprawdza, czy podana jako argument nazwa funkcji jest w rzeczywistości funkcją, którą możemy wywołać za pomocą call_user_func(). Od strony technicznej - sprawdza, czy podana jako parametr funkcja jest typu callable/callback. Typy callback i callable oznaczają to samo, z tym że callable został wprowadzony dopiero w wersji 5.4 PHP.
Następnie, o ile funkcja istnieje jest ona wywoływana za pomocą call_user_func() z argumentem, w którym przekazywany jest jej wynik jaki obliczyliśmy.

Zobaczmy co się stanie po wywołaniu naszej funkcji:
[code]
$my_number = 16;
sqrtWithCallback($my_number, 'myCallback');
[/code]
Voila! Zostanie nam wyświetlone:
[code]My result is: 4[/code]
Funkcja zwrotna (callback) odebrała od funkcji bazowej wynik jej działania, następnie przetworzyła go po swojemu, wyświetlając nam wynik. Zmieńmy naszą funkcję obliczającą pierwiastek na trochę bardziej elegancką:
[code]
function sqrtWithCallback($number, callable $callback)
{
  $res = sqrt($number);
  if(!is_callable($callback) || !call_user_func($callback, $res)) {
    return false;
  }
  return $res;
}
[/code]
Będzie działać tak samo, ale jednocześnie zwracac nam wynik w przypadku powodzenia, lub false w przypadku niemożliwości wywołania funkcji zwrotnej.

CALLBACK I KLASY

A jakim sposobem przesłać tutaj jako callback nie funkcję, a np. metodę klasy?
Bardzo podobnie jak w przypadku call_user_func(). Utwórzmy najpierw przykładową klasę:
[code]
class MyCallbacks {

  public static function sqrtCallback($input)
  {
     echo 'My result is: ' . $input . '<br />';
  }

  public function sqrtCallbackObj($input)
  {
     echo 'My result is: ' . $input . '<br />';
  }
}
[/code]
Następnie jako callback podajmy nazwę statycznej metody:
[code]
$my_number = 16;
$sqrt = sqrtWithCallback($my_number, 'MyCallbacks::sqrtCallback');
[/code]
Voila! Tym razem jako callback posłużyła nam metoda statyczna klasy MyCallbacks.

Analogicznie z utworzeniem obiektu klasy:
[code]
$obj = new MyCallbacks;
$sqrt = sqrtWithCallback($my_number, array($obj, 'sqrtCallbackObj'));
[/code]

CALLBACK I FUNKCJE ANONIMOWE

Dochodzimy więc do senda tego artykułu, a więc możliwości połączenia funkcji anonimowych z callbackiem.
W przykładzie powyżej podaliśmy jako callback zwykłą funkcję, zadeklarowaną normalnie, teraz zobaczmy jak podać tutaj funkcję anonimową. Można to zrobić na kilka sposobów. Na początek zadeklarujmy sobie taką anonimową funkcję:
[code]
$callback_func = function($input)
{
  echo 'My result is: ' . $input . '<br />';
};
[/code]
Wywołajmy teraz funkcję bazową:
[code]
$my_number = 16;
sqrtWithCallback($my_number, $callback_func);
[/code]
Bingo! Ponownie otrzymalismy wynik z naszej funkcji zwrotnej, tym razem anonimowej, ale zadeklarowanej do zmiennej.

Prawdziwa jednak siła funkcji anonimowych tkwi w możliwości ich definiowania bezpośrednio w argumencie, popatrzmy:
[code]
$my_number = 16;
$sqrt = sqrtWithCallback($my_number, function($input) {
   echo 'My result is: ' . $input . '<br />';  
});
[/code]
Jak widzimy - deklarację naszej anonimowej funkcji podajemy bezpośrednio w argumencie funkcji ją przyjmującej.
Takiego też sposobu używać będziemy najczęściej, gdyż jest najbardziej ergonomiczny.
Warto to sobie przećwiczyć na własnych przykładach, gdyż zastosowań tego typu mechanizmu jest ogrom.

Więcej o funkcjach anonimowych i callbackach przeczytasz w manualu PHP:
http://php.net/manual/en/function.call-user-func.php
http://php.net/manual/en/function.call-user-func-array.php
http://php.net/manual/en/function.is-callable.php
http://php.net/manual/en/language.types.callable.php
http://php.net/manual/en/language.pseudo-types.php#language.types.callback

Na sam koniec jeszcze kod, który pokaże nam wszystkie sposoby działające po koleji:
[code]
<?php
// #1 - przypiszemy anonimową funkcje do zmennej $callback
$callback = function($input)
{
  echo '#1: My result is: ' . $input . '<br />';
};


// #2 -  normalna funkcja
function myCallback($input)
{
  echo '#2: My result is: ' . $input . '<br />';
}


// #3 i #4 - metody w klasie
class MyCallbacks {

  public function sqrtCallbackObj($input)
  {
     echo '#3: My result is: ' . $input . '<br />';
  }

  public static function sqrtCallback($input)
  {
     echo '#4: My result is: ' . $input . '<br />';
  }  
}


// nasza funkcja przekazująca wynik do callbacka
function sqrtWithCallback($number, callable $callback)
{
  $res = sqrt($number);
  if(!is_callable($callback) || !call_user_func($callback, $res)) {
    return false;
  }
  return $res;
}


$my_number = 16;

// #1 wywołanie za pomocą zmiennej zawierającej funkcję:
$sqrt = sqrtWithCallback($my_number, $callback);


// #2 wywołanie za pomocą podania nazwy funkcji:
$sqrt = sqrtWithCallback($my_number, 'myCallback');


// #3 metoda obiektu klasy MyCallbacks:
$obj = new MyCallbacks;
$sqrt = sqrtWithCallback($my_number,array($obj, 'sqrtCallbackObj'));


// #4 metoda statyczna w klasie MyCallbacks:
$sqrt = sqrtWithCallback($my_number, 'MyCallbacks::sqrtCallback');


// #5 funkcja anonimowa z deklaracją w parametrze:
$sqrt = sqrtWithCallback($my_number, function($input) {
   echo '#5: My result is: ' . $input . '<br />';  
});
?>
[/code]

6 komentarzy:

  1. Ładnie wszystko opisane, chociaż artykuł nie wyczerpuje tematu. Warto np. wspomnieć o możliwości wywołania callbacka w metodzie klasy, gdzie callbackiem jest inna metoda tej samej klasy:
    call_user_func_array(array($this, 'methodName'), $arguments);
    W ostatnim kodzie źródłowym masz błąd. W lini 45 przekazujesz funkcję anonimową w zmiennej $callback:
    $sqrt = sqrtWithCallback($my_number, $callback);
    natomiast na początku kodu przypisujesz ją do innej zmiennej ($callback_func) :)

    OdpowiedzUsuń
    Odpowiedzi
    1. Opiszę trochę więcej w następnych artykułach, na razie pokazałem jedynie podstawy. Dzięki za zwrócenie uwagi na błąd w kodzie - już poprawiłem. Pozdrawiam :)

      Usuń
  2. Aktualnie analizuję model MVC i spotkałem w nim parę rzeczy które opisałeś w swoim artykule. Jednym słowem bardzo dobra robota ... bo zaoszczędziłeś mi sporo czasu :)

    OdpowiedzUsuń
  3. A czy ktoś może mi wytłumaczyć po co stosować callbacki skoro są funkcje anonimowe?

    OdpowiedzUsuń
    Odpowiedzi
    1. Callbacki i funkcje anonimowe to jest coś odrębnego. Jako callback możesz wykorzystać zwykłą funkcję lub funkcję anonimową.

      Usuń
  4. a co moge zrobić funkcją anonimową lub czego nie moge zrobić bez niej

    OdpowiedzUsuń

Masz sugestię? Znalazłeś błąd? Napisz komentarz! :)

webmaester.pl - profesjonalne projektowanie WWW i webaplikacji
webmaester.pl - profesjonalne projektowanie WWW i webaplikacji