Під час роботи над IndexNow у Laravel мені потрібно було очистити список URL перед відправкою: прибрати порожні значення, видалити дублікати й відновити послідовні numeric keys.
Це був звичайний невеликий transformation pipeline, але саме в такому коді я вперше використала PHP 8.5 Pipe Operator |> не як демонстрацію нової syntax, а як частину реального production-коду.
У результаті код став читатися в тому самому напрямку, в якому змінюються дані: filter → unique → values.
На той момент ICanUp уже працював на PHP 8.5, тому я могла оцінювати нову syntax не в ізольованому прикладі, а безпосередньо під час розвитку застосунку.
Реальний кейс з'явився в IndexNow client
У реалізації IndexNow для автоматичної відправки URL у Bing HTTP client отримує масив public URLs і формує urlList для зовнішнього request.
Перед request мені потрібно було гарантувати, що список не містить порожніх значень або дублікатів і має нормальні послідовні indexes.
Це не окрема користь заради Pipe Operator. Ця логіка вже була потрібна самому IndexNow contract.
$urls = array_filter( $urls, static fn(mixed $url): bool => is_string($url) && $url !== '', ) |> array_unique(...) |> array_values(...);Мені подобається, що цей фрагмент майже не потребує перекладу на людську мову: я фільтрую URLs, беру unique values і в кінці reindex масив.
array_filter()- прибирає все, що не є непорожнім stringarray_unique()- прибирає повторні URLarray_values()- повертає послідовні numeric keysРезультат - чистий список, готовий до
urlListу JSON payload
Що саме робить `|>` у PHP 8.5
Pipe Operator бере результат expression зліва і передає його callable справа.
Праву частину можна читати як функцію, яку PHP викликає з попереднім результатом.
Тому простий pipe не створює нового типу collection або спеціального pipeline object. Це звичайний expression на рівні самої мови.
$result = $value |> transform(...); $result = transform($value);Як цей код виглядав би без Pipe Operator
Ту саму операцію можна написати звичайними вкладеними function calls. Вона буде працювати так само.
$urls = array_values( array_unique( array_filter( $urls, static fn(mixed $url): bool => is_string($url) && $url !== '', ), ),);Тут немає технічної проблеми. Але порядок виконання я читаю зсередини назовні.
Спочатку потрібно знайти array_filter(), потім піднятися до array_unique(), а після цього - до зовнішнього array_values().
Коли transformations стає більше, ця вкладеність починає приховувати саму послідовність дій.
Альтернатива з тимчасовими змінними теж хороша
$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, ....
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.
Чому `array_filter()` у мене залишився перед pipe
На перший погляд можна захотіти почати chain прямо з $urls |> ...
Але для array_filter() мені потрібен не лише input array, а й callback з validation rule.
Найпростіше було залишити цей перший виклик звичайним expression, а вже його result передати в однопараметрові callable steps.
$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.
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
$result = $input |> (fn($value) => transform($value, $config, $locale)) |> (fn($value) => validateValue($value, $rules, $context)) |> (fn($value) => formatOutput($value, $options, $timezone));Якщо майже кожен рядок перетворюється на окрему closure з додатковими dependencies, я вже не отримую тієї простоти, заради якої починала pipeline.
Є ще одна очевидна межа: середовище виконання повинно бути PHP 8.5
Pipe Operator - синтаксис PHP 8.5. Це означає, що недостатньо оновити лише локальний PHP.
CLI, PHP-FPM, CI runner, Docker workspace, Composer platform requirements і production runtime мають бути узгоджені до того, як у codebase з'явиться |>.
Інакше проблема буде вже не в читабельністі, а в тому, що старіший інтерпритатор просто не зможе парсити новий синтаксис.
{ "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 | Кілька етапів застосування/домену | Структура й окремі обов'язки |
Після рефакторингу я перевіряю поведінку, а не красу синтаксису
Нерядкові значення не потрапляють у фінальний URL list.
Порожні рядки видаляються.
Однакові URLs не дублюються.
Після filtering і deduplication keys знову послідовні.
Порожній результат не запускає HTTP request.
Фінальний
urlListзалишається JSON array.IndexNow HTTP payload після рефакторингу синтаксису має ту саму функціональну поведінку.
Що я залишила собі на майбутнє
|>найкраще працює для невеликих послідовних 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 у правильному напрямку.



