Przejdź do treści

Troszkę o funkcjach anonimowych

Trochę wstępu

Funkcje anonimowe w wielu językach zwane funkcjami / wyrażeniami lambda, nie są niczym nowym w świecie programowania a tym bardziej nie są jakimś wymysłem języka PHP, ponieważ można znaleźć je w wielu innych językach, w PHP są od 28 czerwca 2000 r. dokładnie od wersji 4.0.1, jak możemy wyczytać z change loga

Added create_function(), which gives the ability to create functions on-the-fly (Zeev, Zend Engine)

Natomiast kolejnym ważnym krokiem w funkcjonowaniu i postrzeganiu funkcji anonimowej w PHP jest wersja 5.3.
Na przestrzeni kolejnych wydań języka PHP, także i one (tzn. funkcje anonimowe dalej zwane FA) ewoluowały w wersji 5.47.0 oraz w najnowszym wydaniu 7.1 (na dzień pisania wpisu jest to najnowsza wersja). Kolejną ważna wersją dla FA będzie wersja 7.2.0, gdzie została ona oznaczona jako (na chwilę obecną wersja 7.2.0 ma status beta) DEPRECATED.

This function has been DEPRECATED as of PHP 7.2.0. Relying on this function is highly discouraged.

Pozwól, że w tym wpisie przyjrzymy się im troszkę bardziej, dla bardziej doświadczonych koderów temat może wydać się nudny.

A więc do dzieła

Zacznijmy od tego jak zadeklarować taką funkcję anonimową, dla przykładu przypiszmy ją do zmiennej $var

$var = function() {
    return 'nasza funkcja anonimowa';
};

Super, mamy pierwszą funkcje anonimową przypisaną na zmiennej $var, ale jak ją teraz wywołać ?
…a no tak jak każdą inną funkcję a więc $var().
Co więcej jeśli nasza funkcja anonimowa przyjmuje parametry np.

$var = function($param1, $param2) {
    return "parametr 1: {$param1} oraz parametr 2 : {$param2}";
}

To powyższą funkcję wywołujemy następująco: $var('wartość parametru 1', 'wartość parametru 2');
Prawda że żaden rocket science ?
zobaczmy co zwróci nam var_dump dla powyższego przykładu

object(Closure)#1 (0) {
}

A więc nasza funkcja anonimowa jest instancja obiektu Closure, zaraz zaraz funkcja jest obiektem i żeby było tego mało, obiekt 'przedstawia’ nam się jako domknięcie ?

Closure

Definicja domknięcia brzmi następująco (źródło wikipedia):

Domknięcie – w metodach realizacji języków programowania jest to obiekt wiążący funkcję lub referencję do funkcji oraz środowisko mające wpływ na tę funkcję w momencie jej definiowania. Środowisko przechowuje wszystkie nielokalne obiekty wykorzystywane przez funkcję. Realizacja domknięcia jest zdeterminowana przez język, jak również przez kompilator.

I ważna informacja nie każda funkcja anonimowa jest domknięciem ! ! !

Powyższą informację radzę zapisać, wydrukować, zapamiętać !

Środowisko programistów w podejściu do domknięć jest bardzo podzielone, niektórzy uważają, że wystarczy wydzielić osobny zakres (scope) dla danego obiektu / zmiennych i to staje się już domknięciem, co by wskazywało na to, że każda funkcja anonimowa staje się domknięciem (a przecież tak nie jest, co przed chwilą wspominałem).
Dla zobrazowania o co chodzi tutaj przykład

$zmienna1 = 'bcb';

$toNieJestDomkniecie = function() {
    $zmienna1 = 'abc';
    $zmienna2 = 'abc2';
    echo $zmienna1; // 'abc'
};
$toNieJestDomkniecie();

Aby powyższy przykład stał się domknięciem, musi on wedle definicji mieć dostęp do nielokalnych zasobów (przez zasób mam na myśli obiekt lub zmienną), na poniższym przykładzie pokaże Ci które zasoby są nielokalne dla przyszłego domknięcia.

$zmienna1 = 'abc'; // to nie jest zmienna lokalna
$obiekt = new SplFixedArray(1); // to nie jest obiekt lokalny

$fa = function() {
    $zmienna2 = 'bcb'; // to jest zmienna lokalna
    $obiekt2 = new SplFixedArray(1); // to jest obiekt lokalny
}

Jeżeli w funkcji $fa będziemy chcieli odwołać się do zasobu nielokalnego np. $zmienna1 czeka na nas niestety okrutny error `Undefined variable: zmienna1`.
Jeżeli chcemy użyć zasobu nielokalnego musimy dowiązać taki zasób z naszym domknięciem, w PHP robi się to za pomocą operatora use pamiętaj, że dowiązanie tworzy się przez wartość ale można wymusić referencje. Oto i przykład.

$zmienna1 = 'abc'; // to nie jest zmienna lokalna
$obiekt = new SplFixedArray(1); // to nie jest obiekt lokalny

$fa = function() use ($zmienna1, &$obiekt) {
    // $zmienna1 jest już dostępna z tego poziomu, została dowiązana jej wartość
    // $obiekt jest już dostępny z tego poziomu, został dowiązany przez referencje

    $zmienna2 = 'bcb'; // to jest zmienna lokalna
    $obiekt2 = new SplFixedArray(1); // to jest obiekt lokalny
}

Closures w PHP

A więc jeśli już wiemy, że nasze funkcje anonimowe w PHP to domknięcia a bardziej szczegółowo, są obiektem klasy Closure, to przyjrzyjmy się troszkę bardziej owej klasie.
Posiada ona 5 metod z czego 2 to statyczne metody, jedna to konstruktor co więcej jest on prywatny, oraz dwie publiczne metody.

Closure {
/* Methods */
    private __construct ( void )
    public static Closure bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] )
    public Closure bindTo ( object $newthis [, mixed $newscope = "static" ] )
    public mixed call ( object $newthis [, mixed $... ] )
    public static Closure fromCallable ( callable $callable )
}

Skoro konstruktor jest zadeklarowany jako prywatny, to „jedynym” sposobem na utworzenie instancji tego obiektu jest zastosowanie wyżej wymienionego sposobu deklarowania funkcji anonimowej.
Kolejnym ważnym elementem jest to iż metoda call została dodana wraz z wersją 7.0 i jest ona małym 'shorthandem’ dla metody bindTo.
Metody obiektu closure służą nam do wykonywania lub powiązania domknięcia z danym obiektem lub jego kontekstem, inaczej mówiąc, możemy wykonać domknięcie w obrębie danego obiektu używając jego kontekstu, co więcej można w dowolny sposób modyfikować ten obiekt.
Metoda call pozwala na natychmiastowe wykonanie domknięcia z wykorzystaniem jego kontekstu. natomiast metody bind oraz bindTo służą do powiązania danego domknięcia z reprezentantem klasy, co skutkuje tym, że do końca wykonania skryptu powiązanie jest aktywne co nie zawsze może nam się podobać.
Sprawdźmy teraz jak wykonać wybrane domknięcie w obrębie testowej klasy, najpierw utwórzmy przykładowe domknięcie

$tekst = 'hello';

$domkniecie = function() use ($tekst) {
    echo $tekst . ' ' . $this->tekst;
};

a teraz utwórzmy przykładową klasę i jej obiekt

class A {
    private $tekst = 'world !';
}

$a = new A();

następnie poskładajmy wszystko w całość i wykonajmy domknięcie w obrębie kontekstu klasy A

$tekst = 'hello';

$domkniecie = function() use ($tekst) {
    echo $tekst . ' ' . $this->tekst;
};

class A {
    private $tekst = 'world !';
}

$a = new A();

$domkniecie->call($a);

i na ekranie zobaczymy piękne hello world! ponieważ domknięcie zostało wykonane w kontekście obiektu klasy A, co więcej bez problemu możemy zmodyfikować taki obiekt, oto przykład

$tekst = 'hello';

$domkniecie = function() use ($tekst) {
    $this->tekst = ' closure';
    echo $tekst . ' ' . $this->tekst;
};

class A {
    private $tekst = 'world !';
}

$a = new A();

$domkniecie->call($a);

Teraz dostaniemy na ekranie piękny tekst hello closure.
Zwróć uwagę że zakres widoczności pola został ustawiony jako private i dalej mamy dostęp do danego pola co więcej nadal możemy je dowolnie zmieniać.
Dla czystej formalności przedstawię powyższy przykład z użyciem bindTo zamiast call.
Przy bindTo możemy wskazać dowolny obiekt lub klasę !

$tekst = 'hello';

$domkniecie = function() use ($tekst) {
    echo $tekst . ' ' . $this->tekst;
};

class A {
    private $tekst = 'world !';
}

$a = new A();

$domkniecie = $domkniecie->bindTo($a, $a); // ponieważ bind oraz bindTo zwraca nam nowe domknięcie już z dowiązanym kontekstem
$domkniecie();

Zanim zakończymy, powyższe przykłady działają od wersji 5.3+ a przecież wspomniałem, że można utworzyć FA już od wersji 4.0.1, masz rację i poniżej przedstawię jak tworzy się takie wyrażenie lambda w alternatywny sposób którego nie jestem fanem i nie polecam, ale niestety muszę o nim wspomnieć skoro wpis ma być kompetentny.
A więc od wersji 4.0.1 do wersji 7.2.0 możemy FA zadeklarować w poniższy sposób (analogicznie do powyższych przykładów):

$var = create_function('', "return 'nasza funkcja anonimowa';");

oraz przykład z argumentami funkcji

$var = create_function('$param1, $param2', '  return "parametr 1: {$param1} oraz parametr 2 : {$param2}";');

Wykonanie powyższych 2 funkcji jest takie same jak wszystkich innych pozostałych funkcji a więc $var().
Ciekawostką jest natomiast co zwraca var_dump dla tak utworzonej funkcji.

string(9) "lambda_1"

Zwróć uwagę na cyfrę na końcu zwróconej zwrotki, jest ona licznikiem wyrażeń lambda w naszym kodzie, analogicznie do licznika #<N> gdzie N jest cyfrą, w przypadku var_dump dla obiektów, przykład poniżej

$var = function() {
    return 'nasza funkcja anonimowa';
};

var_dump($var);

// var_dump : 
//  object(Closure)#1 (0) {
//  }

Czas kończyć

Na zakończenie dodam, że wyżej w tekście wspomniałem iż jedynymi sposobami na utworzenie domknięcia jest użycie zapisu function… ale można by pokusić się o użycie np. ReflectionAPI lub jak każdy inny obiekt, także i Closure posiada magiczne __clone() oraz __wakeup() , co pozwala nam na utworzenie wielu instancji tego samego domknięcia.
Pamiętaj, że domknięcia są bardzo potężnym narzędziem w pracy każdego developera, nie każdy musi je rozumieć ale każdy musi przynajmniej wiedzieć co one potrafią i czy mogą nam uprzykrzyć życie, w następnych wpisach postaram się pokazać jak bardzo mogą nam pomóc lub zaszkodzić.
I tym miłym akcentem kończę mój pierwszy wpis na blogu (początki są zawsze trudne) mam nadzieję drogi czytelniku, że dotrwałeś do tego momentu a na Twojej twarzy nie pojawiło się zniesmaczenie, a jeżeli masz jakieś uwagi nie krępuj się zostaw informację w komentarzu.

 

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *