26 września 2025 | Autor: Łukasz Premik

Na czym polega programowanie funkcyjne w PHP?

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.

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.0

Dlaczego 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 polem

PHP 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 = 220

Domknię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 parzystych

Tightenco/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.

mgr inż. Łukasz Premik
Łukasz PremikMagister Inżynier IT / Konsultant ds. Infrastruktury

Zajmuję się doradztwem technicznym, pozycjonowaniem stron oraz tworzeniem nowoczesnych aplikacji dla firm. Pomagam markom bezboleśnie przechodzić przez procesy cyfryzacji i migracji systemów IT.

Powiązane artykuły

Chcesz wdrożyć nowoczesne rozwiązania w swojej firmie?

Niezależnie od tego, czy potrzebujesz wsparcia IT, nowej strony www, szybkiego sklepu, czy automatyzacji – skontaktuj się ze mną, a chętnie pomogę.

Napisz do mnie