Знайомтеся з Piper: маніпуляції з array та string за допомогою pipe operator

Перекладено ШІ 0 Spatie 30 липня, 2026

Бібліотека Piper адаптує стандартний API PHP під pipe operator, додаючи масивам та рядкам ергономіку Laravel Collections. Це елегантний спосіб писати чистий лінійний код без зайвих проміжних змінних чи громіздких обгорток.

Оператор pipe став важливим доповненням у PHP 8.5. Він поєднує ергономіку об’єктів-обгорток із гнучкістю окремих функцій. На жаль, він поки не набув масового вжитку. Стандартні функції PHP для маніпуляцій з array та string відомі своєю непослідовністю, що робить використання оператора pipe доволі незручним.

Piper — це спроба адаптувати стандартну бібліотеку PHP так, щоб вона стала сумісною з оператором pipe.

Пакет містить хелпери для array:

use function Spatie\Piper\Arr\{filter, map};

$popular = $posts
    |> filter(fn (Post $post) => $post->views > 1000)
    |> map(fn (Post $post) => $post->title);

// ["Claude Talk Small. Code Still Big.", …]

Та хелпери для string:

use function Spatie\Piper\Str\{lower, replace};

'Hello, world!'
    |> lower()
    |> replace('world', 'Piper')
  
// "hello, Piper!"

Оскільки всі функції працюють із примітивами, їх можна вільно комбінувати:

use function Spatie\Piper\Arr\{filter, join, map, values};
use function Spatie\Piper\Str\{prefix, suffix};

[1, 2, 3, 4, 5, 6]
    |> filter(fn (int $i) => $i % 2 === 0)
    |> map(fn (int $i) => pow($i, 2))
    |> values()
    |> join(', ', ', and ')
    |> prefix('The winning numbers are ')
    |> suffix('.');

// "The winning numbers are 4, 16, and 36."

Piper доступний на GitHub, його можна встановити через Composer:

composer require spatie/piper

Чому оператор pipe — це золота середина

Оператор pipe створений для того, щоб зробити послідовний код чистим і зрозумілим.

Без нього нам доводиться створювати купу проміжних змінних, коли ми хочемо виконати кілька операцій над одним значенням.

$orders = [
    ['id' => 1, 'status' => 'paid', 'total' => 120],
    ['id' => 2, 'status' => 'pending', 'total' => 80],
    ['id' => 3, 'status' => 'paid', 'total' => 45],
    ['id' => 4, 'status' => 'paid', 'total' => 200],
];

$largePaidOrders = array_filter(
    $orders,
    fn (array $order) =>
        $order['status'] === 'paid' && $order['total'] > 100
);

$report = array_map(fn (array $order) => [
    'id' => $order['id'],
    'amount' => '$' . number_format($order['total'], 2),
], $largePaidOrders);

Якщо ми хочемо позбутися проміжних змінних, можна передавати результат попередньої функції безпосередньо в наступну. Але тоді код доведеться писати у порядку, зворотному до його виконання.

$report = array_map(
    fn (array $order) => [
        'id' => $order['id'],
        'amount' => '$' . number_format($order['total'], 2),
    ],
    array_filter($orders, fn (array $order) =>
        $order['status'] === 'paid' && $order['total'] > 100
    )
);

На перший погляд важко навіть визначити, де починається цей вираз (підказка: зі змінної $orders всередині виклику array_filter).

У чистому PHP (vanilla PHP) доводиться обирати між випадковими проміжними змінними або кодом-«матріошкою», де все вкладено навиворіт.

Інший варіант — обгорнути масив в об'єкт із fluent API, наприклад, у чудовий Collection від Laravel.

$report = collect($orders)
    ->filter(fn (array $order) =>
        $order['status'] === 'paid' && $order['total'] > 100
    )
    ->map(fn (array $order) => [
        'id' => $order['id'],
        'amount' => '$' . number_format($order['total'], 2),
    ])
    ->all();

Проте об’єкти-обгортки мають власні недоліки:

  • Їх потрібно «розпаковувати» для подальшого використання (як-от виклик ->all() вище).
  • Їх важко розширювати. Якщо потрібен метод для специфічної доменної логіки, доведеться створювати кастомний об'єкт колекції. Це шкодить композиції, оскільки код розсіюється між різними класами.

Тут на сцену виходить оператор |>. Результат попереднього виразу передається у наступну функцію в ланцюжку. Це дозволяє використовувати звичайні функції, не обгортаючи дані в об’єкти, і при цьому зберігати логічний порядок читання коду.

$report = $orders
    |> (fn (array $orders) => array_filter($orders, fn (array $order) =>
        $order['status'] === 'paid' && $order['total'] > 100
    ))
    |> (fn (array $orders) => array_map(fn (array $order) => [
        'id' => $order['id'],
        'amount' => '$' . number_format($order['total'], 2),
    ], $orders));

У поточному вигляді оператор pipe має обмеження: функція, в яку ми передаємо дані, повинна приймати рівно один аргумент. Якщо це не так, доводиться використовувати closure.

Приклад, що повертає всі парні числа з масиву:

[1, 2, 3, 4]
    |> (fn (array $numbers) => array_filter($numbers, fn (int $i) => $i % 2 === 0));

Крім того, стандартна бібліотека PHP тягне за собою вантаж минулого з хаотичним порядком аргументів. Тому, попри наявність оператора pipe у мові, він не завжди може розкрити свій потенціал.

Piper: бібліотека утиліт, орієнтована на pipe

Piper — це адаптація методів Laravel для роботи з колекціями та рядками, оптимізована саме для оператора pipe.

use function Spatie\Piper\Arr\filter;

[1, 2, 3, 4]
    |> filter(fn (int $i) => $i % 2 === 0);

Повернемося до прикладу зі звітом $report:

use function Spatie\Piper\Arr\{filter, map};

$report = $orders
    |> filter(fn (array $order) => 
        $order['status'] === 'paid' && $order['total'] > 100
    )
    |> map(fn (array $order) => [
        'id' => $order['id'],
        'amount' => '$' . number_format($order['total'], 2),
    ]);

Це вирішує всі проблеми: жодних зайвих змінних, а порядок коду відповідає порядку виконання. Ідеально!

Що означає «оптимізовано для оператора pipe»? Кожна функція є функцією вищого порядку (higher-order function), яка повертає функцію, що приймає рівно один аргумент.

Ось як виглядає функція filter у Piper:

function filter(?callable $callback = null): Closure
{
    return function (array $items) use ($callback): array {
        return $callback === null
            ? array_filter($items)
            : array_filter($items, $callback, ARRAY_FILTER_USE_BOTH);
    };
}

Без оператора pipe виклик виглядав би так:

filter(fn (int $i) => $i % 2 === 0)([1, 2, 3, 4]);
// [1 => 2, 3 => 4]

Аргументи, передані у зовнішню функцію, створюють нову функцію, яка вже обробляє вхідні дані. Це називається частковим застосуванням (partial application), яке стане ще зручнішим у PHP 8.6.

Piper підтримує більшість функцій для роботи з рядками та колекціями з Laravel. Повний список доступний у README.

Погляд у майбутнє

Можливо, я ніколи не буду використовувати цю бібліотеку. Можливо, я взагалі не буду використовувати оператор pipe. Ера «агентного програмування» та ШІ поступово нівелює значення синтаксичної естетики. Програмування майбутнього буде зосереджене на результативності виконання. Боротьба за те, щоб код «виглядав красиво», здається, вщухає — ШІ зміщує наш фокус на результат, а не на чистоту кожного рядка. Це не обов’язково погано, але частина нашого ремесла може бути втрачена.

Іронічно, що я наважився написати Piper саме тому, що маю під рукою ШІ. Ручне портування 234 функцій і написання тестів — це не те, чим я хотів би займатися. Натомість час, витрачений на розробку цього пакета, вимірюється годинами.

У будь-якому разі, це цікавий експеримент. Я планую попрацювати з ним у реальних проєктах. Якщо ідея приживеться, наступним кроком стане spatie/laravel-piper з глибшою інтеграцією в Laravel (наприклад, підтримка Arrayable, LazyCollection тощо без попереднього приведення до масиву).

Популярні

Інше, що варто прочитати

38 Оновлено 26 червня, 2026

4 поширені помилки Vite у Laravel

Використання Vite для створення фронтенд-ресурсів у вашому додатку Laravel може бути захоплюючим, але іноді ви можете стикнутися з певними помилками. У цій статті ми розглянемо чотири поширені помилки, з якими ви можете зіткнутися, а також підкажемо способи їх усунення, щоб ви могли знову зосередитися на розробці вашого додатку

16 Оновлено 26 червня, 2026

Журнал аудиту в Laravel

Хочете забезпечити повну прозорість у своїх Laravel-додатках? Пакет Laravel Audit Log допоможе вам детально відстежувати всі зміни моделей Eloquent та відповідати вимогам регуляторів. Читайте далі, щоб дізнатися, як цей потужний інструмент може підвищити надійність вашого проєкту

14 Оновлено 26 червня, 2026

Локальні моделі та їх скоупи в Laravel за допомогою атрибута Scope

В Laravel 12 ми отримали можливість використовувати новий підхід для визначення локальних скоупів у моделях Eloquent. Дізнайтеся, як новий атрибут #[Scope] спрощує цей процес і зберігає ваші назви методів незмінними