Lekcja 11: Funkcje Anonimowe (Closures) i Funkcje Strzałkowe (Arrow Functions)
Witaj w jedenastej lekcji kursu PHP! W poprzedniej lekcji nauczyliśmy się definiować i używać tradycyjnych, nazwanych funkcji. PHP, jako dynamicznie rozwijający się język, oferuje również bardziej nowoczesne i elastyczne sposoby definiowania funkcji, takie jak funkcje anonimowe (znane również jako domknięcia lub closures) oraz, od PHP 7.4, zwięzłe funkcje strzałkowe (arrow functions). Te konstrukcje są szczególnie przydatne w programowaniu funkcyjnym, przy obsłudze zdarzeń, jako argumenty zwrotne (callbacks) oraz w wielu innych scenariuszach, gdzie potrzebujemy małych, jednorazowych funkcji. W tej lekcji zgłębimy te zaawansowane koncepty.
Funkcje Anonimowe (Closures / Domknięcia)
Funkcja anonimowa to funkcja bez nazwy. Jest ona często przypisywana do zmiennej, która następnie może być używana do wywołania tej funkcji, lub przekazywana jako argument do innej funkcji (np. jako callback).
Składnia definicji funkcji anonimowej jest podobna do zwykłej funkcji, ale słowo kluczowe function
nie jest poprzedzone nazwą:
<?php
$mojaFunkcjaAnonimowa = function($parametr1, $parametr2) {
// Ciało funkcji anonimowej
return $parametr1 + $parametr2;
};
// Wywołanie funkcji anonimowej za pomocą zmiennej
$wynik = $mojaFunkcjaAnonimowa(10, 20);
echo "Wynik z funkcji anonimowej: $wynik<br>"; // 30
// Funkcja anonimowa jako callback dla array_map
$liczby = [1, 2, 3, 4, 5];
$kwadraty = array_map(function($n) {
return $n * $n;
}, $liczby);
echo "Kwadraty liczb: <pre>";
print_r($kwadraty);
echo "</pre>";
// Array ( [0] => 1 [1] => 4 [2] => 9 [3] => 16 [4] => 25 )
?>
Ważną cechą funkcji anonimowych w PHP jest to, że mogą one być domknięciami (closures). Oznacza to, że mogą one "dziedziczyć" zmienne z zakresu, w którym zostały zdefiniowane, nawet jeśli ten zakres już nie istnieje. Aby funkcja anonimowa mogła używać zmiennych z zakresu nadrzędnego, należy je jawnie przekazać za pomocą konstrukcji use ($zmienna1, $zmienna2, ...)
.
<?php
$wiadomosc = "Witaj z zakresu nadrzędnego!";
$mnoznik = 2;
// Funkcja anonimowa dziedzicząca $wiadomosc przez wartość
$wyswietlWiadomosc = function() use ($wiadomosc) {
echo htmlspecialchars($wiadomosc) . "<br>";
// $wiadomosc = "Zmieniona wewnątrz"; // Nie wpłynie na oryginalną $wiadomosc
};
$wyswietlWiadomosc(); // Witaj z zakresu nadrzędnego!
echo "Oryginalna wiadomość: " . htmlspecialchars($wiadomosc) . "<br>"; // Nadal "Witaj z zakresu nadrzędnego!"
// Funkcja anonimowa dziedzicząca $mnoznik przez referencję
$pomnozLiczby = function($liczba) use (&$mnoznik) {
$mnoznik++; // Modyfikujemy $mnoznik z zakresu nadrzędnego
return $liczba * $mnoznik;
};
echo "Mnożnik przed wywołaniem: $mnoznik<br>"; // 2
echo "Wynik mnożenia (5 * (2+1)): " . $pomnozLiczby(5) . "<br>"; // 15
echo "Mnożnik po pierwszym wywołaniu: $mnoznik<br>"; // 3
echo "Wynik mnożenia (10 * (3+1)): " . $pomnozLiczby(10) . "<br>"; // 40
echo "Mnożnik po drugim wywołaniu: $mnoznik<br>"; // 4
// Przykład z array_map i dziedziczeniem zmiennej
$dodatkowaWartosc = 10;
$liczby = [1, 2, 3];
$liczbyPlusDodatkowa = array_map(function($n) use ($dodatkowaWartosc) {
return $n + $dodatkowaWartosc;
}, $liczby);
print_r($liczbyPlusDodatkowa); // Array ( [0] => 11 [1] => 12 [2] => 13 )
echo "<br>";
?>
Konstrukcja use
pozwala funkcji anonimowej "zamknąć" w sobie zmienne z otaczającego ją zakresu. Domyślnie zmienne są przekazywane przez wartość (kopiowane). Aby przekazać je przez referencję (umożliwiając modyfikację oryginalnej zmiennej), należy użyć ampersanda (&
) przed nazwą zmiennej w klauzuli use
.
Typowanie i Wartości Zwracane w Funkcjach Anonimowych
Podobnie jak w przypadku nazwanych funkcji, można stosować typowanie argumentów i wartości zwracanych w funkcjach anonimowych.
<?php
// declare(strict_types=1);
$dodajSilnieTypowane = function(int $a, int $b): int {
return $a + $b;
};
echo "Wynik (silnie typowane): " . $dodajSilnieTypowane(5, 3) . "<br>"; // 8
// echo $dodajSilnieTypowane(5.5, 3); // Spowoduje TypeError w trybie ścisłym
$formatujDane = function(array $dane, string $separator = ", "): string {
return implode($separator, $dane);
};
$mojaTablica = ["jabłko", "banan", "gruszka"];
echo "Sformatowane dane: " . $formatujDane($mojaTablica) . "<br>"; // jabłko, banan, gruszka
echo "Sformatowane dane (inny separator): " . $formatujDane($mojaTablica, " | ") . "<br>"; // jabłko | banan | gruszka
?>
Zastosowania Funkcji Anonimowych
- Funkcje zwrotne (callbacks): Są idealne do przekazywania jako argumenty do funkcji takich jak
array_map()
,array_filter()
,array_reduce()
,usort()
itp. - Krótkie, jednorazowe funkcje: Gdy potrzebujemy małej funkcji do wykonania specyficznego zadania w jednym miejscu i nie chcemy zaśmiecać globalnej przestrzeni nazw.
- Programowanie sterowane zdarzeniami: W systemach obsługi zdarzeń, gdzie funkcje anonimowe mogą służyć jako handlery zdarzeń.
- Wzorzec strategii: Mogą być używane do implementacji różnych strategii w sposób dynamiczny.
- Domknięcia (Closures): Kiedy potrzebujemy funkcji, która "pamięta" pewien stan z otaczającego ją zakresu.
Funkcje Strzałkowe (Arrow Functions) - od PHP 7.4
Funkcje strzałkowe, wprowadzone w PHP 7.4, oferują bardziej zwięzłą składnię dla prostych funkcji anonimowych. Są one szczególnie przydatne, gdy funkcja zawiera tylko jedną instrukcję return
.
Kluczowe cechy funkcji strzałkowych:
- Zaczynają się od słowa kluczowego
fn
. - Mogą mieć listę parametrów, typowanie argumentów i typ wartości zwracanej.
- Po parametrach następuje "strzałka"
=>
, a następnie pojedyncze wyrażenie, które jest wartością zwracaną przez funkcję (słowo kluczowereturn
jest niejawne i nie może być użyte). - Automatycznie dziedziczą zmienne z zakresu nadrzędnego przez wartość. Nie ma potrzeby używania klauzuli
use
. Jeśli chcesz zmodyfikować zmienną z zakresu nadrzędnego, musisz użyć tradycyjnej funkcji anonimowej zuse (&$zmienna)
. - Nie można w nich używać bloków kodu
{ }
(tylko pojedyncze wyrażenie).
Składnia funkcji strzałkowej:
<?php
// fn (parametry): typ_zwracany => wyrażenie_zwracane
$liczby = [1, 2, 3, 4, 5];
$mnoznik = 10;
// Przykład z array_map używający funkcji strzałkowej
// $mnoznik jest automatycznie dostępny wewnątrz funkcji strzałkowej
$pomnozoneLiczby = array_map(fn($n) => $n * $mnoznik, $liczby);
echo "Pomnożone liczby (funkcja strzałkowa): <pre>";
print_r($pomnozoneLiczby);
echo "</pre>";
// Array ( [0] => 10 [1] => 20 [2] => 30 [3] => 40 [4] => 50 )
// Funkcja strzałkowa z typowaniem
$dodajStrzalkowa = fn(int $a, int $b): int => $a + $b;
echo "Wynik (funkcja strzałkowa): " . $dodajStrzalkowa(7, 8) . "<br>"; // 15
// Porównanie z tradycyjną funkcją anonimową
$dodajAnonimowa = function(int $a, int $b) use ($mnoznik): int {
// $mnoznik tutaj nie jest automatycznie dostępny, musiałby być w `use`
// Aby ten przykład był analogiczny, musielibyśmy go przekazać przez `use`
// np. use ($mnoznik) - ale funkcje strzałkowe robią to automatycznie
return $a + $b; // + $mnoznik; // jeśli byśmy go użyli
};
// Przykład, gdzie funkcja strzałkowa jest bardziej zwięzła
$osoby = [
["imie" => "Anna", "wiek" => 30],
["imie" => "Piotr", "wiek" => 25],
["imie" => "Ewa", "wiek" => 35]
];
// Sortowanie tablicy obiektów/tablic asocjacyjnych
// Tradycyjna funkcja anonimowa
usort($osoby, function($a, $b) {
return $a["wiek"] <=> $b["wiek"]; // Operator <=> (spaceship) do porównań
});
// Funkcja strzałkowa (znacznie krócej)
// usort($osoby, fn($a, $b) => $a["wiek"] <=> $b["wiek"]);
echo "Posortowane osoby wg wieku: <pre>";
print_r($osoby);
echo "</pre>";
?>
Funkcje strzałkowe są bardzo wygodne dla krótkich operacji, zwłaszcza w kontekście funkcji tablicowych jak array_map
, array_filter
, array_reduce
, czy funkcji sortujących wymagających callbacków.
Ograniczenia Funkcji Strzałkowych
- Mogą zawierać tylko jedno wyrażenie, które jest automatycznie zwracane. Nie można w nich umieszczać wielu instrukcji ani bloków kodu.
- Automatycznie dziedziczą zmienne z zakresu nadrzędnego tylko przez wartość. Nie można ich modyfikować ani dziedziczyć przez referencję za pomocą
use
. Jeśli potrzebujesz modyfikować zmienną z zakresu nadrzędnego, musisz użyć standardowej funkcji anonimowej zuse (&$zmienna)
. - Nie można w nich używać słowa kluczowego
return
jawnie.
Kiedy używać Funkcji Anonimowych, a kiedy Strzałkowych?
- Funkcje strzałkowe (
fn
):- Idealne dla bardzo krótkich, jednowyrażeniowych funkcji, które coś zwracają.
- Gdy potrzebujesz automatycznego dostępu (przez wartość) do zmiennych z zakresu nadrzędnego bez pisania
use
. - Często używane z
array_map
,array_filter
itp. dla zwięzłości.
- Tradycyjne funkcje anonimowe (
function
):- Gdy funkcja wymaga wielu instrukcji lub bardziej złożonej logiki (bloki kodu
{ }
). - Gdy potrzebujesz jawnie kontrolować, które zmienne z zakresu nadrzędnego są dziedziczone i jak (przez wartość czy przez referencję za pomocą
use
). - Gdy potrzebujesz zmodyfikować zmienną z zakresu nadrzędnego (
use (&$zmienna)
). - Gdy funkcja nie musi nic zwracać lub ma wiele punktów wyjścia (wiele instrukcji
return
).
- Gdy funkcja wymaga wielu instrukcji lub bardziej złożonej logiki (bloki kodu
Podsumowanie Lekcji
W tej lekcji zgłębiliśmy świat funkcji anonimowych (closures) i funkcji strzałkowych w PHP. Zrozumieliśmy, jak definiować funkcje anonimowe, jak działają domknięcia i jak używać klauzuli use
do dziedziczenia zmiennych z zakresu nadrzędnego. Poznaliśmy również zwięzłą składnię funkcji strzałkowych (od PHP 7.4), ich zalety oraz ograniczenia, w tym automatyczne dziedziczenie zmiennych.
Te zaawansowane konstrukcje funkcyjne znacznie zwiększają elastyczność i ekspresywność języka PHP, umożliwiając pisanie bardziej zwięzłego i nowoczesnego kodu, szczególnie w kontekście programowania funkcyjnego i obsługi zdarzeń. W następnej lekcji zajmiemy się bardzo ważnym tematem obsługi błędów i wyjątków w PHP, co jest kluczowe dla tworzenia stabilnych i niezawodnych aplikacji.
Zadanie praktyczne
Wykonaj następujące zadania, używając funkcji anonimowych i/lub strzałkowych:
- Dana jest tablica liczb:
$liczby = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
. Użyj funkcjiarray_filter()
i funkcji strzałkowej, aby wybrać tylko liczby parzyste. - Dana jest tablica stringów:
$slowa = ["jabłko", "Banan", "POMARAŃCZA", "gruszka"];
. Użyj funkcjiarray_map()
i funkcji anonimowej (tradycyjnej), aby przekształcić każdy string na zapisany małymi literami. - Napisz funkcję o nazwie
generatorPowitan
, która przyjmuje jako argument string$powitanie
(np. "Witaj" lub "Cześć"). Ta funkcja powinna zwracać funkcję anonimową, która z kolei przyjmuje jeden argument$imie
i wyświetla spersonalizowane powitanie używając$powitania
z zakresu nadrzędnego (użyjuse
). Przetestuj, tworząc dwa różne generatory powitań i wywołując zwrócone funkcje. - Dana jest tablica asocjacyjna:
$produkty = [["nazwa" => "Chleb", "cena" => 3.50], ["nazwa" => "Masło", "cena" => 6.20], ["nazwa" => "Mleko", "cena" => 2.80]];
. Użyj funkcjiusort()
i funkcji strzałkowej, aby posortować produkty rosnąco według ceny.
Pokaż przykładowe rozwiązanie
<?php
// 1. Filtrowanie liczb parzystych
$liczby = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$parzyste = array_filter($liczby, fn($n) => $n % 2 === 0);
echo "<h3>Liczby parzyste:</h3><pre>";
print_r($parzyste);
echo "</pre>";
// 2. Konwersja stringów na małe litery
$slowa = ["jabłko", "Banan", "POMARAŃCZA", "gruszka"];
$maleLitery = array_map(function(string $s): string {
return strtolower($s);
}, $slowa);
echo "<h3>Słowa małymi literami:</h3><pre>";
print_r($maleLitery);
echo "</pre>";
// 3. Generator powitań
function generatorPowitan(string $powitanie): callable {
return function(string $imie) use ($powitanie): void {
echo htmlspecialchars($powitanie) . ", " . htmlspecialchars($imie) . "!<br>";
};
}
$witajGenerator = generatorPowitan("Witaj serdecznie");
$czescGenerator = generatorPowitan("Cześć");
echo "<h3>Wygenerowane powitania:</h3>";
$witajGenerator("Anna"); // Witaj serdecznie, Anna!
$czescGenerator("Piotr"); // Cześć, Piotr!
// 4. Sortowanie produktów według ceny
$produkty = [
["nazwa" => "Chleb", "cena" => 3.50],
["nazwa" => "Masło", "cena" => 6.20],
["nazwa" => "Mleko", "cena" => 2.80],
["nazwa" => "Ser", "cena" => 5.00]
];
usort($produkty, fn($a, $b) => $a["cena"] <=> $b["cena"]);
echo "<h3>Produkty posortowane wg ceny:</h3><pre>";
print_r($produkty);
echo "</pre>";
?>
Zadanie do samodzielnego wykonania
Stwórz tablicę użytkowników, gdzie każdy użytkownik jest tablicą asocjacyjną zawierającą id
, imie
i aktywny
(boolean). Napisz skrypt, który używa funkcji array_filter()
i funkcji strzałkowej do wybrania tylko aktywnych użytkowników. Następnie, używając funkcji array_map()
i funkcji strzałkowej, stwórz nową tablicę zawierającą tylko imiona aktywnych użytkowników, każde poprzedzone prefiksem "Użytkownik: ".
FAQ - Funkcje Anonimowe i Strzałkowe
Jaka jest główna różnica między funkcją anonimową a funkcją strzałkową?
Funkcje strzałkowe (fn
) są bardziej zwięzłą formą dla prostych, jednowyrażeniowych funkcji anonimowych i automatycznie dziedziczą zmienne z zakresu nadrzędnego przez wartość. Tradycyjne funkcje anonimowe (function
) pozwalają na bloki kodu, jawną kontrolę dziedziczenia zmiennych (use
) i modyfikację zmiennych nadrzędnych przez referencję.
Czy funkcje strzałkowe mogą modyfikować zmienne z zakresu nadrzędnego?
Nie, funkcje strzałkowe dziedziczą zmienne z zakresu nadrzędnego tylko przez wartość. Oznacza to, że mają dostęp do ich wartości, ale nie mogą ich modyfikować w sposób, który wpłynąłby na oryginalne zmienne. Do tego celu należy użyć tradycyjnej funkcji anonimowej z use (&$zmienna)
.
Czy mogę używać typowania w funkcjach strzałkowych?
Tak, funkcje strzałkowe wspierają typowanie argumentów oraz typ wartości zwracanej, tak samo jak tradycyjne funkcje i funkcje anonimowe. Składnia to np. fn(int $a, string $b): string => ...
.
Co to jest "domknięcie" (closure) w kontekście funkcji anonimowych?
Domknięcie to funkcja, która "pamięta" środowisko (zakres zmiennych), w którym została utworzona. W PHP funkcje anonimowe mogą działać jak domknięcia, jeśli użyją klauzuli use
do zaimportowania zmiennych z zakresu nadrzędnego.
Kiedy funkcje strzałkowe zostały dodane do PHP?
Funkcje strzałkowe zostały wprowadzone w wersji PHP 7.4. Jeśli pracujesz ze starszą wersją PHP, nie będziesz mógł z nich korzystać i będziesz musiał używać tradycyjnych funkcji anonimowych.
Czy funkcja anonimowa może być rekurencyjna?
Tak, ale wymaga to pewnego obejścia. Ponieważ funkcja anonimowa nie ma nazwy, nie może bezpośrednio wywołać samej siebie. Można to osiągnąć, przypisując funkcję anonimową do zmiennej, a następnie przekazując tę zmienną do funkcji anonimowej przez referencję za pomocą klauzuli use
. Na przykład: $factorial = function($n) use (&$factorial) { ... $factorial($n-1) ... };
.
Czy $this
jest dostępne w funkcjach anonimowych i strzałkowych zdefiniowanych wewnątrz metod klas?
W tradycyjnych funkcjach anonimowych (function
) zdefiniowanych wewnątrz metody klasy, $this
nie jest automatycznie dostępne. Aby uzyskać do niego dostęp, trzeba je jawnie powiązać za pomocą $closure->bindTo($this, $this)
lub od PHP 5.4 można użyć use ($this)
. Natomiast funkcje strzałkowe (fn
) automatycznie dziedziczą $this
z otaczającego zakresu klasy, tak jakby były częścią metody.