Domknięcia oraz funkcje anonimowe w przykładach …
A więc czas i na część 2, w której to dalej przyjrzymy się przykładowym zastosowaniom domknięć, w tej części głównie skupimy się na drugim typie callbacków, w poprzedniej części nazwałem je tworzącymi jakiś większy 'zamysł’. Jeśli nie czytałeś poprzedniej części to zapraszam do zapoznania się z nią przed przystąpieniem do poniższego wpisu. Dodatkowo jeśli nie bardzo orientujesz się w domknięciach oraz funkcjach anonimowych to zapraszam do wpisu rozpoczynającego serię. Oto i linki: Wpis rozpoczynający serię o funkcjach anonimowych oraz Domknięcia oraz funkcje anonimowe w przykładach część 1
Skoro wstęp mamy już za sobą, przejdźmy teraz do pierwszego zagadnienia tej części czyli do
Lazy Loading
Do czasu gdy w PHP
nie pojawiły się domknięcia, zagadnienia odnośnie lazy loadingu
(implementacja oraz rozważania na jego temat) były rozwiązywane wszelkimi dostępnymi obejściami niedoskonałości języka. Na szczęście te czasy minęły i miejmy nadzieję, że już bezpowrotnie. Obecnie możemy się cieszyć z tak potężnego narzędzia jakim niewątpliwie jest lazy loading
.

Może zanim przyjrzymy się przykładowi, w skrócie opowiem o idei mechanizmu. A więc, lazy loading
jest to przygotowanie obiektu najwcześniej jak to tylko możliwe ale jego użycie (a co za tym idzie, powołanie obiektu do 'życia’) następuje tylko w momencie gdy tak naprawdę go potrzebujemy użyć. Ale po co ? (ktoś może zapytać), no a więc po to (śpieszę z odpowiedzią) aby zminimalizować zużycie zasobów sprzętowych oraz przyśpieszyć naszą aplikację. Bo po co ładować X obiektów skoro do przetworzenia danego żądania wystarczą nam np. 2 (gdzie X > 2).
$lazy = function() { $przykladowyObiekt = new \SplFixedArray(1000); return $przykladowyObiekt; };
W powyższym przykładzie opakowujemy tworzenie obiekty SplFixedArray
w mechanizm lazy loadingu
, teraz gdy będziemy potrzebować tego obiektu, wystarczy odwołać się do domknięcia $lazy()
. Bardziej życiowym przykładem jest przygotowywanie obiektów w kontenerze zależności (DI Container) lub w jakimkolwiek mechanizmie utrzymującym stan naszej aplikacji lub obiektów.
Modelowanie Encji
Wyobraźmy sobie, że nie możemy lub nie chcemy w naszym następnym projekcie użyć żadnego ORM’a oraz chcemy mieć ładną czystą nieedytowalną encje, przez nieedytowalną rozumiem bez encje bez setterów co uniemożliwi nam zmianę jej stanu, natomiast reszta systemu może w jakimś stopniu oddziaływać na nią. Ok więc mamy jakiś kawałek kodu który wyciąga nam dane z bazy i zwraca nam je w postaci tablicowej np.
$dane = [ 'name' => 'Jan', 'age' => 21 ];
Teraz dla przykładu chcielibyśmy aby nasza encja wyglądała mniej więcej tak
class Person { private $name = ''; private $age = 0; public function getName() { return $this->name; } public function getAge() { return $this->age(); } }
Dodatkowo encja ta będzie tworzona przez wyspecjalizowaną do tego celu fabrykę, która utworzy ją na podstawie przekazanej tablicy elementów z bazy danych. Początkowy stan encji oczywiście można by ustawić poprzez konstruktor, natomiast dla tego przykładu nie robimy tego, ponieważ w trakcie działania naszej aplikacji stan tej encji może się zmienić lecz nie poprzez nasze działanie ale poprzez jakąś 'ewolucje’ obiektu. Metoda w naszej fabryce może wyglądać mniej więcej tak
public function newEntity($data) { $entity = new Person(); foreach($data as $key => $value) { (function() use ($key, $value) { $this->{$key} = $value; })->call($entity); } return $entity; }
Natomiast funkcja modyfikująca encje będzie wyglądać bardzo podobnie
public function modifyEntity($data, $entity) { foreach($data as $key => $value) { (function() use ($key, $value) { $this->{$key} = $value; })->call($entity); } return $entity; }
Powyższe metody potrafią przypisać odpowiednią wartość do wskazanej własności w klasie. Oczywiście wprawny programista od razu zawoła ejj hejj ! refaktoryzuj z tych dwóch metod zrób trzy, przez co zlikwidujemy dublet kodu… Oczywiście ma racje, natomiast jest to tylko przykład i refaktoryzacja nie jest tematem tego wpisu 😉
Powyższy przykład jest tak naprawdę bardzo prostym rozwinięciem tego tematu, rozwiązań oraz możliwości jest tak dużo, że postanowiłem w jak najprostszy sposób ukazać te zagadnienie.
Prototypowanie Obiektu
Pewnie nie raz miałeś do czynienia z JavaScriptem, więc mechanizm prototypowania nie jest Ci obcy, a gdyby tak spróbować przenieść ten mechanizm do PHP
(chociaż spróbować go za symulować), niewątpliwie funkcje anonimowe oraz wcześniej poznana metoda call
na pewno nam w tym pomogą.
Na początek stwórzmy sobie podstawową klasę
class Object { protected $methods = []; public function prototype($name, $fn) { $this->methods[$name] = $fn; return $this; } public function __call($name, $args=null) { return $this->methods[$name]->call($this, $args); } public function __invoke() { return $this; } }
Będzie ona naszym rodzicem dla każdego kolejnego bytu, teraz wystarczy tylko utworzyć pierwszą prototypową funkcję.
$obj = new Object(); $class = $obj()->prototype('extend', function() {return clone $this;});
OK a więc nasz obiekt obj
uzyskał nową 'metodę’ o nazwie extend
, cały obiekt zaś przypisaliśmy do zmiennej class
gdyż to ona będzie punktem wyjścia dla kolejnych bytów. W taki sposób definiujemy sobie prototypy naszych klas aby później utworzyć z nich już końcowe obiekty.
$helloWorldClass = $class->extend()->prototype('sayHello', function($args){ echo "Hello {$args[0]}"; }); $helloWorldClass->sayHello('world');
Powyższy przykład jest prostą reprezentacją końcowego obiektu na podstawię wcześniej zdefiniowanego prototypu class
.
Oczywiście jest to bardzo prosty przykład, ale na jego podstawie można bez żadnego problemu tworzyć konkretne prototypy obiektów. Do uzupełnienia tego przykładu dodam, że potrzebowalibyśmy zaimplementować jeszcze metodę new
najlepiej za pomocą prototypu tuż przed metodą extend
.
Na zakończenie ciekawostka
Skoro funkcje anonimowe są tak naprawdę obiektem to dlaczego możemy ich używać jak zwykłych funkcji ? Już śpieszę z odpowiedzią, wszystko za sprawą metody magicznej __invoke
która w locie dodaje do danej klasy typ callable
. Co więcej metodę __invoke
możemy dodać także do swoich klas, dzięki czemu będziemy w stanie używać ich jak zwykłej funkcji, musimy pamiętać aby odpowiednio zaprogramować zachowanie takiego obiektu. Nie wierzysz ? sprawdź sam, czy przykładowa klasa bez jak i z metodą __invoke
jest typu callable
czy też nie. Przykład takiej klasy, został zaprezentowany w tym wpisie a dokładnie w przydał się przy prototypowaniu.
Jak widać zastosowanie domknięć jest bardzo szerokie, praktycznie na każdym kroku można spotkać ich użycie lub możliwość zastosowania. Wszystko zależy tylko i wyłącznie od sytuacji oraz argumentów przyświecających za użyciem domknięcia lub też funkcji anonimowej.