Presentiamo Piper: manipolazione di array e stringhe con l'operatore pipe
L'operatore pipe è stata una grande aggiunta in PHP 8.5. Porta l'ergonomia degli oggetti wrapper con la componibilità delle funzioni standalone. Sfortunatamente, non ha ancora un uso diffuso. Le funzioni standard di manipolazione di array e stringhe in PHP non sono esattamente note per la loro coerenza nelle API, il che rende l'operatore pipe scomodo da usare.
Piper è un tentativo di wrappare la libreria standard per renderla compatibile con l'operatore pipe.
Include helper per gli 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.", …]
E helper per le stringhe:
use function Spatie\Piper\Str\{lower, replace};
'Hello, world!'
|> lower()
|> replace('world', 'Piper')
// "hello, Piper!"
Poiché tutte le funzioni lavorano con primitive, puoi mescolarle e abbinarle:
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 è disponibile su GitHub e può essere installato usando Composer.
composer require spatie/piper
Perché l'operatore pipe è il meglio di tutti i mondi
L'operatore pipe esiste per fornire una sintassi pulita per il codice sequenziale.
Senza l'operatore pipe, assegniamo una serie di variabili intermedie quando vogliamo eseguire più operazioni su un valore.
$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);
Se vogliamo liberarci delle variabili intermedie, possiamo passare direttamente il risultato della funzione precedente alla successiva. Ma allora il nostro codice è scritto nell'ordine opposto a quello della sua esecuzione.
$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
)
);
A colpo d'occhio, questo rende molto difficile individuare dove inizia questa istruzione. (È la variabile $orders nella chiamata array_filter.)
Il PHP vanilla ti costringe a scegliere tra variabili intermedie con nomi arbitrari (ugh) o codice annidato al contrario (ugh).
In alternativa, possiamo incapsulare il nostro array in un oggetto con una API fluente, come l'eccellente oggetto Collection di 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();
Ma gli oggetti wrapper hanno il loro insieme di problemi:
- Devi estrarli dal wrapper per usarli altrove; la chiamata
->all()qui sopra. - Non sono estendibili. Se voglio un metodo per qualcosa di specifico del dominio, devo creare un oggetto collection personalizzato. Dato che il mio array ora vive in un oggetto collection personalizzato, perdo anche la componibilità perché il mio codice potrebbe essere sparso su più oggetti collection.
Entra in scena l'operatore pipe |>. Con l'operatore pipe, il risultato dell'istruzione precedente viene passato alla funzione successiva nella pipeline. Questo ci permette di continuare a usare funzioni regolari e componibili senza incapsulare l'array in un oggetto personalizzato, mantenendo l'ordine del codice coerente con l'ordine di esecuzione.
$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));
L'operatore pipe nella sua forma attuale ha un vincolo: la funzione verso cui facciamo piping deve accettare esattamente un argomento. In caso contrario, puoi incapsularla in una closure a singolo argomento per renderla compatibile.
Ecco un esempio che restituisce tutti i numeri pari in un array:
[1, 2, 3, 4]
|> (fn (array $numbers) => array_filter($numbers, fn (int $i) => $i % 2 === 0));
Inoltre, PHP si porta dietro un bel po' di zavorra dal passato e la sua libreria standard è piena di incoerenze nell'ordine degli argomenti. Quindi, anche se abbiamo l'operatore pipe nel linguaggio, non può sempre fiorire.
Piper: la libreria di utilità pipe operator-first
Piper è un port dei metodi di utilità per collection e stringhe di Laravel, ottimizzato per l'operatore pipe.
use function Spatie\Piper\Arr\filter;
[1, 2, 3, 4]
|> filter(fn (int $i) => $i % 2 === 0);
E torniamo al nostro esempio $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),
]);
Questo soddisfa tutti i requisiti:
- Nessuna variabile intermedia
- Il codice è allineato con l'ordine di esecuzione
Che meraviglia!
Cosa significa optimized for the pipe operator? Ogni funzione è una funzione di ordine superiore che restituisce una funzione che ha esattamente un argomento soggetto.
Ecco come appare la funzione filter di 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);
};
}
Senza l'operatore pipe, chiameresti il nostro esempio dei numeri pari così:
filter(fn (int $i) => $i % 2 === 0)([1, 2, 3, 4]);
// [1 => 2, 3 => 4]
Gli argomenti passati alla funzione esterna vengono usati per generare una nuova funzione che accetta l'input. Questa si chiama applicazione parziale, che sta ricevendo un po' di amore extra in PHP 8.6!
Piper supporta la maggior parte delle funzioni per collection e stringhe incluse in Laravel. Un elenco completo è disponibile nel README.
Guardando al futuro
Potrei non usare mai questa libreria. Potrei non usare mai affatto l'operatore pipe. La programmazione agentica sta spingendo la sintassi verso un punto morto. Nuove API di programmazione che portano benefici all'esecuzione verranno portate avanti. Ma l'intera battaglia per far apparire bene il codice sembra essersi spenta, mentre l'IA ci sta lentamente spostando a concentrarci sui risultati più che sulla pulizia del codice riga per riga. Non è necessariamente una cosa negativa, ma parte del nostro mestiere potrebbe andare persa lungo il cammino.
Ironicamente, mi sono convinto a scrivere davvero questo perché ho l'IA a mia disposizione. Portare manualmente 234 funzioni e un'intera suite di test non è qualcosa che aspettavo con ansia. Invece, il tempo speso a costruire e rifinire questo pacchetto si misura in ore.
In ogni caso, questo è un esperimento divertente da condurre. E non vedo l'ora di armeggiare ulteriormente e provarlo in alcuni progetti reali. Se attacca, spatie/laravel-piper sarà il prossimo in linea con una migliore integrazione con Laravel (piping di collection o qualsiasi cosa Arrayable senza prima fare il cast ad array, supporto a LazyCollection, ecc.)
Fonte originale:
https://spatie.be/blog/introducing-piperAltri articoli recenti
Laravel AI 1.1 e 1.2: Agent Skills, Cohere e structured output più solidi
Le release 1.1.0 e 1.2.0 di laravel/ai portano gli Agent Skills, l'approvazione obbligatoria per i tool built-in e nuovi provider come Cohere e Laya self-hosted. Sul fronte qualità arrivano i fix allo structured output per Bedrock e il supporto alle provider options. Aggiornati anche i modelli di default, con Claude 4.7+ che ignora i sampling params.
Laravel 13.35.0: scheduler più controllabile, code più flessibili e response Markdown
Laravel 13.35.0 porta Schedule::alwaysOnOneServer(), il supporto a schedule:interrupt per i comandi di lunga durata, --memory in percentuale per queue:work e il nuovo metodo markdown() sulla response factory. Tante piccole migliorie anche a test, view compilate in parallelo e dischi local.
BashBox: un interprete Bash in sandbox per agenti AI in PHP
BashBox è un interprete bash scritto in puro PHP che permette di eseguire gli script generati dagli agenti AI senza mai ricorrere a proc_open, exec o shell_exec. Ogni comando è una classe PHP che lavora su un filesystem virtuale, con limiti di esecuzione, quote disco e accesso di rete configurabili. Ideale per chi sviluppa agenti con il Laravel AI SDK o altre librerie simili.