Pipe operator w PHP 8.5

rury

PHP 8.5 przynosi niewiele nowego. Bardziej skupia się na dalszym usprawnianiu języka w kwestii spójności i eliminacji jego niekonsekwencji. Jak to w każdej wersji, pojawia się jednak flagowa funkcjonalność. I tak jak w PHP 8.4 były to property hooks, tak w PHP 8.5 jest to pipe operator.

Rury to niewątpliwie kolejny lukier składniowy. Nie jest to coś, bez czego nie da się żyć. Ani nic, co całkowicie zmienia to w jaki sposób programuje się w PHP. Jest to inna forma ekspresji. Mimo wszystko, może być ciekawą alternatywą, która jest w stanie poprawić chociażby czytelność kodu. Sam jeszcze nie wiem, jak dużo będę tego używał w samym PHP, choć muszę przyznać, że jest to coś, co uwielbiam w systemach typu unix. Regularnie sięgam po pipe operator używając terminala.

Pipe operator – co trzeba wiedzieć w PHP

Rurki pozwalają łączyć ze sobą fragmenty kodu w różnych konfiguracjach. Można popatrzeć na to jak na łańcuch wywołań. Oczko po oczku – od lewej do prawej. Wynik poprzedzającej funkcji stanowi dane wejściowe kolejnej funkcji.

Bezdyskusyjnie jest to składnia, która wpasowuje się bardziej w paradygmat funkcyjny. Nie stanie się więc podstawowym elementem języka PHP, w którym nadal programuje się głównie obiektowo. Tak czy inaczej, nawet w podejściu obiektowym wykorzystuje się łączenie wielu funkcji wewnątrz metod i klas odpowiednio enkapsulując logikę. Istnieje więc pole do popisu dla operatora pipe, czego dowodem są poniższe przykłady kodu, które prowadzą do tego samego rezultatu.

<?php

// Temporary variable style
function camelCaseToSnakeCase(string $phrase): string
{
    $phrase = preg_replace('/[A-Z]/', '_$0', $phrase);
    $phrase = strtolower($phrase);
    $phrase = ltrim($phrase, '_');

    return $phrase;
}
<?php

// Nested functions style
function camelCaseToSnakeCase(string $phrase): string
{
    return ltrim(
        strtolower(
            preg_replace('/[A-Z]/', '_$0', $phrase)
        ),
        '_'
    );
}
<?php

// Pipe operator style
function camelCaseToSnakeCase(string $phrase): string
{
    return preg_replace('/[A-Z]/', '_$0', $phrase)
        |> strtolower(...)
        |> fn ($x): string => ltrim($x, '_');
}

Wszystkie trzy wariacje dla wywołania camelCaseToSnakeCase('SikiuaresIsTheBestEbookAboutCqrsInPhp') zwrócą poprawnie sikiuares_is_the_best_ebook_about_cqrs_in_php.

Obrazek posiada pusty atrybut alt; plik o nazwie promo3.jpg

Jednak przy wykorzystaniu rurek trzeba było zrobić pewnego fikołka w ostatniej transformacji. Składnia pipe operator w PHP to |> i dalej jako argument trzykropek (...). Tyle tylko, że na ten moment rury zadziałają, gdy funkcja przyjmuje dokładnie jeden arugment jako rezultat poprzedniej operacji. Nie udałoby się zatem zrobić tego w następujący sposób: ltrim(..., '_'), bo spowoduje to błąd składniowy. Gdyby drugi argument był domyślny, owszem ltrim(...) by zadziałało. W tym przypadku to funkcja strzałkowa delikatnie podratowała i finalny efekt jest zadowalający.

Swoją drogą, brak wskazywania argumentu jawnie to tak zwany point-free style, który pozwala eliminować niepotrzebne zmienne pośrednie, które widać w przykładzie pierwszym. Choć, z tego co się orientuję, to akurat w PHP zazwyczaj zmienne tymczasowe, które prowadzą do zwrócenia wartości, nie zużywają dodatkowej pamięci do momentu jej zapisania (copy-on-write). Rury oferują więc bardziej przejrzysty zapis, ale w kwestii zużywania zasobów zachowują się podobnie do pozostałych opcji.

Elementem łańcucha przekazywanym dalej może być dowolny callable zwracający wartość i przyjmujący dokładnie jeden argument (nie jako referencja). Te z typem zwracanym void mogą być używane w łańcuchu, ale wartość zwracana jest wymuszana na null, co jeżeli już gdziekolwiek miałoby mieć sens to tylko jako ostatnie oczko. Oczywiście, gdy tryb strict_mode jest włączony to wymagana jest ścisła zgodność typów, a jej brak powoduje ewentualny TypeError, w momencie gdy zwrócony typ z poprzedniego oczka jest różny od typu argumentu kolejnego oczka.

Jeżeli chodzi o kolejność to zawsze leci od lewej do prawej, co tak samo jak w przypadku matematycznych operatorów można kontrolować za pomocą nawiasów. Jak się można spodziewać, pipe operator ma wyższy priorytet, niż operatory równości (=, ==, ===), więc wykona się przed nimi.

To co – rury na plus czy minus

Pipe operator dla jednego wywołania sprawdzi się średnio, ale już dla łańcucha wywołań może być ciekawą składnią, która poprawi czytelność. Najlepiej działa z czystymi funkcjami, więc nada się dobrze chociażby do transformacji danych. Większość wywołań jest implementowana w czasie kompilacji z minimalnym obciążeniem czasu wykonania.

Ja osobiście przechodzę obojętnie obok tej nowości. Co i tak jest na plus, bo ostatnie tego typu usprawnienia bardziej krytykowałem, niż chwaliłem. Język PHP jest już na tyle dojrzały, że niektóre rzeczy wymyślane są na siłę. Na szczęście pipe operator wydaje się być nieszkodliwy. Jedni go polubią, inni mniej. Niezależnie od tego, czy będziecie go używać, niewiele to zmienia. Jak pewnie większość z Was, ja bardziej preferuję styl obiektowy, a rury nie wpasowują się zbyt dobrze w ten paradygmat. Pewnie czasem po nie sięgnę, ale zazwyczaj będą lepsze rozwiązania pozwalające odpowiednio dzielić kod na mniejsze i reużywalne komponenty.

Klasycznie, po więcej zachęcam do zajrzenia w RFC.

Programista PHP i właściciel marki Koddlo. Pasjonat czystego kodu i dobrych praktyk programowania obiektowego. Prywatnie fan dobrego humoru i podcastów.