0% прочитано

PHP 8.5 Pipe Operator у реальному Laravel-коді: де |> справді покращує читабельність

Під час роботи над IndexNow у Laravel я використала новий Pipe Operator з PHP 8.5 для послідовного очищення списку URL. На реальному production-коді показую, чим |> відрізняється від вкладених function calls, де pipeline читається краще і де цей синтаксис уже не варто використовувати.

7 вересня 2026 р. 8 хв читанняPHP

Під час роботи над IndexNow у Laravel мені потрібно було очистити список URL перед відправкою: прибрати порожні значення, видалити дублікати й відновити послідовні numeric keys.

Це був звичайний невеликий transformation pipeline, але саме в такому коді я вперше використала PHP 8.5 Pipe Operator |> не як демонстрацію нової syntax, а як частину реального production-коду.

У результаті код став читатися в тому самому напрямку, в якому змінюються дані: filter → unique → values.

Коротко: `|>` найкраще спрацював для мене там, де результат одного невеликого transformation природно стає input наступного. Він не робить старий PHP-код неправильним, він просто дозволяє читати послідовність зліва направо замість вкладеності зсередини назовні.

На той момент ICanUp уже працював на PHP 8.5, тому я могла оцінювати нову syntax не в ізольованому прикладі, а безпосередньо під час розвитку застосунку.

Реальний кейс з'явився в IndexNow client

У реалізації IndexNow для автоматичної відправки URL у Bing HTTP client отримує масив public URLs і формує urlList для зовнішнього request.

Перед request мені потрібно було гарантувати, що список не містить порожніх значень або дублікатів і має нормальні послідовні indexes.

Це не окрема користь заради Pipe Operator. Ця логіка вже була потрібна самому IndexNow contract.

PHP - IndexNow (очищення URL через PHP 8.5 Pipe Operator)
$urls = array_filter(        $urls,        static fn(mixed $url): bool => is_string($url) && $url !== '',    )        |> array_unique(...)        |> array_values(...);

Мені подобається, що цей фрагмент майже не потребує перекладу на людську мову: я фільтрую URLs, беру unique values і в кінці reindex масив.

  • array_filter() - прибирає все, що не є непорожнім string

  • array_unique() - прибирає повторні URL

  • array_values() - повертає послідовні numeric keys

  • Результат - чистий список, готовий до urlList у JSON payload

Що саме робить `|>` у PHP 8.5

Pipe Operator бере результат expression зліва і передає його callable справа.

Праву частину можна читати як функцію, яку PHP викликає з попереднім результатом.

Тому простий pipe не створює нового типу collection або спеціального pipeline object. Це звичайний expression на рівні самої мови.

PHP - Найпростіша еквівалентність Pipe Operator
$result = $value |> transform(...); $result = transform($value);
`array_unique(...)` тут не означає argument unpacking. Це first-class callable syntax: PHP отримує callable на `array_unique()`, а Pipe Operator передає в нього результат попереднього expression.

Як цей код виглядав би без Pipe Operator

Ту саму операцію можна написати звичайними вкладеними function calls. Вона буде працювати так само.

PHP - Той самий transformation через вкладені виклики
$urls = array_values(    array_unique(        array_filter(            $urls,            static fn(mixed $url): bool => is_string($url) && $url !== '',        ),    ),);

Тут немає технічної проблеми. Але порядок виконання я читаю зсередини назовні.

Спочатку потрібно знайти array_filter(), потім піднятися до array_unique(), а після цього - до зовнішнього array_values().

Коли transformations стає більше, ця вкладеність починає приховувати саму послідовність дій.

Альтернатива з тимчасовими змінними теж хороша

PHP - Transformation через послідовні assignments
$urls = array_filter(    $urls,    static fn(mixed $url): bool => is_string($url) && $url !== '',); $urls = array_unique($urls);$urls = array_values($urls);

Цей варіант я теж не вважаю гіршим.

Навпаки, якщо мені потрібно поставити breakpoint між steps, залогувати проміжне значення або дати кожному стану окреме змістовне ім'я, temporary variables можуть бути зрозумілішими за pipe.

Тому для мене питання не в тому, чи можна замінити код на |>, а в тому, чи стає після цього простіше читати намір.

Pipe Operator я використовую не для того, щоб прибрати змінні, а для того, щоб послідовність transformations читалася в природному напрямку.

Чому саме цей IndexNow fragment добре підійшов для `|>`

  • Кожен step невеликий і легко зрозумілий

  • Результат попереднього step є input наступного

  • Steps не мають окремої бізнес-семантики, якій потрібне власне ім'я

  • Мені не потрібні проміжні values після завершення pipeline

  • Послідовність filter → unique → values важливіша за вкладеність function calls

  • Фінальний результат залишається звичайним PHP array

У цьому прикладі є ще один нюанс, через який порядок transformations має значення.

array_filter() зберігає keys. array_unique() теж може залишити прогалини після видалення duplicate values.

Тому після filtering і deduplication indexes масиву вже не обов'язково будуть 0, 1, 2, ....

Як змінюються keys під час очищення URL
Input: 0 => https://example.com/a1 => ''2 => https://example.com/a3 => https://example.com/b  After array_filter(): 0 => https://example.com/a2 => https://example.com/a3 => https://example.com/b  After array_unique(): 0 => https://example.com/a3 => https://example.com/b  After array_values(): 0 => https://example.com/a1 => https://example.com/b

Для IndexNow це не лише питання красивого дампу масиву.

Цей масив далі передається Laravel HTTP client як urlList у JSON payload. У PHP array з непослідовними numeric keys при JSON encoding може бути представлений як object із keys, а не як JSON list.

Тому фінальний array_values() гарантує саме той вигляд, який я хочу відправити зовнішньому API.

У цьому pipeline `array_values()` - не косметичне форматування. Після `array_filter()` і `array_unique()` воно відновлює sequential keys, щоб `urlList` залишався JSON array.

Чому `array_filter()` у мене залишився перед pipe

На перший погляд можна захотіти почати chain прямо з $urls |> ...

Але для array_filter() мені потрібен не лише input array, а й callback з validation rule.

Найпростіше було залишити цей перший виклик звичайним expression, а вже його result передати в однопараметрові callable steps.

PHP - Варіант, де весь flow починається з pipe
$urls = $urls    |> (static fn(array $urls): array => array_filter(        $urls,        static fn(mixed $url): bool => is_string($url) && $url !== '',    ))    |> array_unique(...)    |> array_values(...);

Цей варіант теж виражає pipeline, але заради першого step додає wrapper closure. У моєму випадку початковий `array_filter()` без pipe виявився простішим.

Другий реальний приклад виявився ще коротшим

Ще одне природне місце для `|>` з'явилося під час отримання slug із URL path.

PHP - URL path → slug через Pipe Operator
private function pageSlugCandidate(string $url): ?string{    if ($this->isExternalUrl($url)) {        return null;    }     $path = parse_url($url, PHP_URL_PATH);     if (! is_string($path)) {        return null;    }     $slug = rtrim($path, '/')        |> basename(...)        |> rawurldecode(...);     return $slug !== '' ? $slug : null;}

Тут pipeline читається буквально як transformation path.

Спочатку я прибираю кінцеву косу риску, потім беру basename, після цього декодую отриманий slug.

Саме для таких коротких чистих перетвореннь новий синтаксис для мене виглядає найбільш природно.

Де я не стала б використовувати Pipe Operator

  • Коли кожен step має важливий domain meaning і заслуговує на окреме ім'я

  • Коли проміжні values потрібно перевіряти або логувати

  • Коли більшість steps потребує wrapper closures лише для того, щоб підлаштувати signature

  • Коли chain стає настільки довгим, що його вже складніше читати, ніж кілька assignments

  • Коли transformation має side effects і незрозуміло, що саме повертається далі

  • Коли |> додається лише тому, що це нова можливість PHP 8.5

PHP - Приклад pipeline, який я б не ускладнювала
$result = $input    |> (fn($value) => transform($value, $config, $locale))    |> (fn($value) => validateValue($value, $rules, $context))    |> (fn($value) => formatOutput($value, $options, $timezone));

Якщо майже кожен рядок перетворюється на окрему closure з додатковими dependencies, я вже не отримую тієї простоти, заради якої починала pipeline.

Перед заміною вкладених calls на `|>` я дивлюся не на кількість символів, а на напрямок читання. Якщо після refactor data flow видно швидше, то pipe виправданий.

Є ще одна очевидна межа: середовище виконання повинно бути PHP 8.5

Pipe Operator - синтаксис PHP 8.5. Це означає, що недостатньо оновити лише локальний PHP.

CLI, PHP-FPM, CI runner, Docker workspace, Composer platform requirements і production runtime мають бути узгоджені до того, як у codebase з'явиться |>.

Інакше проблема буде вже не в читабельністі, а в тому, що старіший інтерпритатор просто не зможе парсити новий синтаксис.

JSON - Composer requirement для PHP 8.5 project
{    "require": {        "php": "^8.5"    }}

Сам перехід ICanUp на новий runtime я окремо описувала у статті про PHP 8.5, Laravel, Laradock і Composer package upgrade.

`|>` і Laravel Pipeline - це не одне й те саме

Назви схожі, але я використовую ці два підходи на різних рівнях.

PHP Pipe Operator - це невеликий language-level expression для передачі одного value через callables.

Laravel Pipeline я використовую для більш структурованої application flow, де stages можуть бути окремими класами і мати власні відповідальності. Такий реальний кейс у мене вже був у розрахунку комісій через Laravel Pipeline.

Для трьох array функцій створювати Laravel Pipeline було б зайвим. Для складного domain flow один |> теж не замінює архітектуру.

Підхід

Коли я його використовую

Перевага

Вкладені функції

Короткий простий вираз

Мінімальний синтаксис

Temporary variables

Потрібні проміжні стани або налагодження

Максимальна явність

PHP |>

Наступні невеликі перетворення

Читання зліва направо

Laravel Pipeline

Кілька етапів застосування/домену

Структура й окремі обов'язки

Після рефакторингу я перевіряю поведінку, а не красу синтаксису

  1. Нерядкові значення не потрапляють у фінальний URL list.

  2. Порожні рядки видаляються.

  3. Однакові URLs не дублюються.

  4. Після filtering і deduplication keys знову послідовні.

  5. Порожній результат не запускає HTTP request.

  6. Фінальний urlList залишається JSON array.

  7. IndexNow HTTP payload після рефакторингу синтаксису має ту саму функціональну поведінку.

Для мене хороший refactor із `|>` - це коли tests підтверджують ту саму behavior, а зміна залишається переважно зміною способу читання code flow.

Що я залишила собі на майбутнє

  • |> найкраще працює для невеликих послідовних transformations

  • Не кожен вкладений виклик потрібно переписувати

  • Тимчасові змінні залишаються хорошим рішенням, особливо для debugging

  • Wrapper closure не варто додавати лише для того, щоб усе виглядало як один pipe chain

  • Потрібно дивитися не лише на значення, а й на форму даних між кроками (як у випадку array_values() перед JSON payload)

  • Новий синтаксис PHP варто використовувати після узгодження всього runtime, а не лише локального середовища

Висновок

Мені подобається PHP 8.5 Pipe Operator не тому, що він дозволяє написати той самий код новим способом.

У хорошому місці |> змінює напрямок читання. Замість того щоб розбирати вкладені functions зсередини назовні, я бачу data flow у тому порядку, в якому він реально виконується.

IndexNow URL cleanup став для мене саме таким прикладом: filter → unique → values. Другий case із rtrim → basename → rawurldecode підтвердив те саме.

Я не планую перетворювати весь PHP-код на pipe chains. Але там, де є короткий transformation pipeline без зайвих side effects, |> уже став для мене нормальним інструментом, а не просто новинкою PHP 8.5.

Хороший Pipe Operator не змушує мене думати про Pipe Operator. Він просто дозволяє прочитати transformation у правильному напрямку.