Na czym polega programowanie funkcyjne w PHP?

Odpowiedź na skróty (BLUF): W PHP podejście to bazuje na korzystaniu z zamkniętych wywołań anonimowych i wykorzystaniu czystych funkcji ograniczających zmiany statusu zmiennych, ułatwiając znacząco optymalizację z pomocą tablic iteracyjnych.
Spis Treści
- Czym jest programowanie funkcyjne?
- Czyste funkcje - fundament programowania funkcyjnego
- Niemutowalność - dane, które się nie zmieniają
- Funkcje wyższego rzędu - funkcje jako obywatele pierwszej klasy
- Domknięcia (Closures) i funkcje anonimowe
- Kompozycja funkcji i currying
- Biblioteki wspierające FP w PHP
- Kiedy stosować FP w PHP, a kiedy nie?
PHP kojarzy się głównie z programowaniem obiektowym i proceduralnym. Frameworki jak Laravel, Symfony czy Slim są głęboko zakorzenione w OOP. Tymczasem PHP od wersji 5.3 systematycznie dodaje narzędzia, które otwierają drzwi do zupełnie innego stylu pisania kodu - funkcyjnego. Nie musisz porzucać klas i obiektów, żeby korzystać z jego idei. Wystarczy zmienić sposób myślenia o problemach.
Czym jest programowanie funkcyjne?
Programowanie funkcyjne (FP - Functional Programming) to paradygmat programowania, który traktuje obliczenia jako ewaluację funkcji matematycznych i unika zmieniania stanu oraz danych mutowalnych.
Brzmi abstrakcyjnie, więc ujmijmy to inaczej. W programowaniu imperatywnym mówisz komputerowi jak coś zrobić - krok po kroku, zmieniając stan zmiennych. W programowaniu funkcyjnym mówisz co chcesz osiągnąć, składając funkcje jak klocki.
Trzy filary odróżniające FP od innych podejść to czyste funkcje, niemutowalność i funkcje wyższego rzędu. Każde z nich omówimy szczegółowo z przykładami w PHP.
Czyste funkcje – fundament programowania funkcyjnego
Czysta funkcja to taka, która dla tych samych argumentów zawsze zwraca ten sam wynik i nie ma żadnych efektów ubocznych - nie modyfikuje zewnętrznego stanu, nie zapisuje do bazy danych, nie wypisuje niczego na ekran.
Porównanie czystej i nieczystej funkcji:
// Nieczysta funkcja - zależy od stanu zewnętrznego
$podatek = 0.23;
function obliczCeneBrutto(float $cenaNetto): float {
global $podatek; // Zależy od zmiennej globalnej
return $cenaNetto * (1 + $podatek);
}
// Czysta funkcja - wszystkie dane wejściowe przez argumenty
function obliczCeneBruttoCzysta(float $cenaNetto, float $stawkaPodatku): float {
return $cenaNetto * (1 + $stawkaPodatku);
}
// Wywołania
obliczCeneBruttoCzysta(100.0, 0.23); // Zawsze 123.0
obliczCeneBruttoCzysta(100.0, 0.08); // Zawsze 108.0Dlaczego czyste funkcje są cenne? Po pierwsze są łatwe do testowania - nie potrzebujesz mockowania zewnętrznego stanu, wystarczy przekazać argumenty i sprawdzić wynik. Po drugie są przewidywalne - widzisz argumenty, wiesz co dostaniesz. Po trzecie można je bezpiecznie uruchamiać równolegle - brak efektów ubocznych eliminuje problemy z wyścigami wątków.
Niemutowalność – dane, które się nie zmieniają
W programowaniu funkcyjnym zamiast modyfikować istniejące dane, tworzysz nowe z uwzględnionymi zmianami. To eliminuje całą klasę błędów związanych z nieoczekiwaną modyfikacją stanu.
// Podejście mutowalne - modyfikujemy oryginalną tablicę
$produkty = [
['nazwa' => 'Laptop', 'cena' => 3000],
['nazwa' => 'Mysz', 'cena' => 150],
['nazwa' => 'Monitor', 'cena' => 1200],
];
// Zła praktyka - modyfikujemy oryginał
foreach ($produkty as &$produkt) {
$produkt['cenaBrutto'] = $produkt['cena'] * 1.23;
}
// Podejście funkcyjne - tworzymy nową tablicę
$produktyZPodatkiem = array_map(
fn($produkt) => [...$produkt, 'cenaBrutto' => $produkt['cena'] * 1.23],
$produkty
);
// $produkty pozostaje niezmienione
// $produktyZPodatkiem to nowa tablica z dodatkowym polemPHP nie wymusza niemutowalności jak języki czysto funkcyjne (Haskell, Elm), ale możesz stosować ją jako konwencję. Klasy readonly wprowadzone w PHP 8.1 oraz readonly właściwości pomagają egzekwować niemutowalność na poziomie obiektów:
// PHP 8.2 - readonly class
readonly class Punkt {
public function __construct(
public float $x,
public float $y
) {}
// Zamiast modyfikować, zwracamy nowy obiekt
public function przesun(float $dx, float $dy): self {
return new self($this->x + $dx, $this->y + $dy);
}
}
$punkt = new Punkt(1.0, 2.0);
$nowypunkt = $punkt->przesun(3.0, 4.0);
// $punkt->x = 5.0; // TypeError - readonly!Funkcje wyższego rzędu – funkcje jako obywatele pierwszej klasy
W PHP funkcje są wartościami - można je przypisywać do zmiennych, przekazywać jako argumenty i zwracać z innych funkcji. To otwiera drzwi do funkcji wyższego rzędu: funkcji, które przyjmują lub zwracają inne funkcje.
PHP ma wbudowane funkcje wyższego rzędu do pracy z tablicami, które są podstawą funkcyjnego stylu:
$liczby = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// array_map - transformuj każdy element
$kwadraty = array_map(fn($n) => $n ** 2, $liczby);
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// array_filter - filtruj elementy spełniające warunek
$parzyste = array_filter($liczby, fn($n) => $n % 2 === 0);
// [2, 4, 6, 8, 10]
// array_reduce - zredukuj do jednej wartości
$suma = array_reduce($liczby, fn($carry, $n) => $carry + $n, 0);
// 55
// Łączenie operacji - pipeline danych
$wynik = array_reduce(
array_map(
fn($n) => $n ** 2,
array_filter($liczby, fn($n) => $n % 2 === 0)
),
fn($carry, $n) => $carry + $n,
0
);
// Suma kwadratów parzystych: 4 + 16 + 36 + 64 + 100 = 220Domknięcia (Closures) i funkcje anonimowe
Domknięcie (closure) to funkcja anonimowa, która "zamyka się" nad zmiennymi z zewnętrznego zakresu. W PHP domknięcia tworzy się przez słowo kluczowe use lub przez arrow functions (fn):
// Closure z use
$mnoznik = 3;
$pomnoz = function(int $n) use ($mnoznik): int {
return $n * $mnoznik;
};
// Arrow function - automatycznie przechwytuje zmienne z zakresu
$pomnozArrow = fn(int $n) => $n * $mnoznik;
echo $pomnoz(5); // 15
echo $pomnozArrow(5); // 15
// Funkcja wyższego rzędu tworząca zamknięcia - fabryka funkcji
function stworzMnoznik(int $mnoznik): Closure {
return fn(int $n) => $n * $mnoznik;
}
$podwoj = stworzMnoznik(2);
$potroј = stworzMnoznik(3);
echo $podwoj(5); // 10
echo $potroј(5); // 15
// Użycie w array_map
$liczby = [1, 2, 3, 4, 5];
$podwojone = array_map($podwoj, $liczby);
// [2, 4, 6, 8, 10]Kompozycja funkcji i currying
Dwie techniki z serca programowania funkcyjnego, które w PHP można implementować ręcznie.
Kompozycja funkcji to łączenie funkcji w łańcuch, gdzie wynik jednej staje się argumentem następnej:
php
function compose(callable ...$fns): Closure {
return function ($value) use ($fns) {
return array_reduce(
array_reverse($fns),
fn($carry, $fn) => $fn($carry),
$value
);
};
}
$usunSpacje = fn(string $s) => trim($s);
$zamienNaMale = fn(string $s) => strtolower($s);
$zastapSpacjeDywizem = fn(string $s) => str_replace(' ', '-', $s);
$stworzSlug = compose($zastapSpacjeDywizem, $zamienNaMale, $usunSpacje);
echo $stworzSlug(' Jak Pisać Artykuły SEO ');
// "jak-pisać-artykuły-seo"Currying to technika przekształcania funkcji przyjmującej wiele argumentów w serię funkcji przyjmujących jeden argument:
// Funkcja z trzema argumentami
function dodajPodatek(float $stawka, float $cena): float {
return $cena * (1 + $stawka);
}
// Wersja curried - zwraca funkcję czekającą na drugi argument
function dodajPodatekCurried(float $stawka): Closure {
return fn(float $cena) => $cena * (1 + $stawka);
}
$dodajVAT = dodajPodatekCurried(0.23);
$dodajVATObnizone = dodajPodatekCurried(0.08);
$cenyNetto = [100.0, 250.0, 75.0];
$cenyVAT = array_map($dodajVAT, $cenyNetto);
// [123.0, 307.5, 92.25]
$cenyVATObnizone = array_map($dodajVATObnizone, $cenyNetto);
// [108.0, 270.0, 81.0]Biblioteki wspierające FP w PHP
Kilka bibliotek ułatwia programowanie funkcyjne w PHP, szczególnie przy bardziej zaawansowanych wzorcach.
Ramda PHP (port popularnej biblioteki Ramda.js) dostarcza zestaw funkcji utility w stylu FP - automatyczne currying, kompozycja, operacje na kolekcjach.
Laravel Collections - jeśli używasz Laravela, Collection API to de facto funkcyjne programowanie ukryte pod wygodnym interfejsem obiektowym:
$wynik = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
->filter(fn($n) => $n % 2 === 0)
->map(fn($n) => $n ** 2)
->reduce(fn($carry, $n) => $carry + $n, 0);
// 220 - suma kwadratów parzystychTightenco/Collect - standalone wersja Laravel Collections bez zależności od całego frameworka.
Kiedy stosować FP w PHP, a kiedy nie?
Programowanie funkcyjne w PHP nie jest rozwiązaniem na wszystko. Ma wyraźne obszary zastosowań.
Funkcyjne podejście sprawdza się przy transformacjach danych - pipeline przetwarzania kolekcji, mapowaniach, filtrach i agregacjach. Logice biznesowej gdzie przewidywalność i testowalność są kluczowe - czyste funkcje są trywialne do unit testowania. Middleware i pipeline'ach - kompozycja funkcji jest naturalnym wzorcem dla middleware HTTP, pipelines przetwarzania zdarzeń czy łańcuchów walidacji.
Tradycyjne OOP lub podejście proceduralne może być lepsze przy operacjach I/O (bazy danych, pliki, API) gdzie efekty uboczne są nieuniknione, zarządzaniu złożonym stanem aplikacji i teamach bez doświadczenia z FP - wymuszanie czysto funkcyjnego stylu w zespole niezaznajomionym z paradygmatem może prowadzić do większego kłopotu niż korzyści.
Najlepsze podejście to pragmatyczna kombinacja - OOP jako architektura, FP jako styl implementacji logiki wewnątrz klas i serwisów. Czyste funkcje tam gdzie to możliwe, efekty uboczne zgrupowane i odizolowane.
Jeśli masz pytania dotyczące konkretnych wzorców FP w PHP lub chcesz omówić refactoring istniejącego kodu - napisz przez formularz kontaktowy.




