Magento 2 e PHP 8.5: come usare il Pipe Operator per scrivere codice più pulito

magento 2 php 8.5 vantaggi

PHP 8.5 introduce diverse novità interessanti per gli sviluppatori, ma una delle più semplici — e potenzialmente più utili nel codice Magento — è il nuovo pipe operator |>.

Con Magento 2.4.9 e il supporto a PHP 8.5 possiamo iniziare a utilizzare questa sintassi anche nello sviluppo di moduli Magento.

Non si tratta di una rivoluzione dell’architettura Magento. Il pipe operator non introduce funzionalità che prima fossero impossibili da realizzare.

Il vantaggio è un altro: rende molto più leggibili le sequenze di trasformazioni applicate a un dato.

Ed è proprio un pattern che incontriamo continuamente nello sviluppo Magento.

Cos’è il Pipe Operator di PHP 8.5

Consideriamo una semplice trasformazione:

$value = trim($value);
$value = strtolower($value);
$value = htmlspecialchars($value);

Oppure, utilizzando funzioni annidate:

$value = htmlspecialchars(
    strtolower(
        trim($value)
    )
);

Con PHP 8.5 possiamo rappresentare lo stesso flusso tramite il pipe operator:

$value = $value
    |> trim(...)
    |> strtolower(...)
    |> htmlspecialchars(...);

La lettura diventa immediatamente sequenziale:

valore
→ trim
→ lowercase
→ escape
→ risultato

Il valore prodotto da ogni operazione viene passato alla funzione successiva.

Il vantaggio diventa evidente quando le trasformazioni non sono due o tre semplici funzioni PHP, ma servizi applicativi appartenenti al nostro modulo Magento.

Perché è interessante nello sviluppo Magento 2

Chi sviluppa estensioni Magento incontra continuamente codice di questo tipo:

$data = $this->normalizer->normalize($data);
$data = $this->validator->validate($data);
$data = $this->enricher->enrich($data);
$data = $this->mapper->map($data);

Concettualmente abbiamo già costruito una pipeline.

PHP 8.5 ci permette semplicemente di rappresentarla come tale:

$result = $data
    |> $this->normalizer->normalize(...)
    |> $this->validator->validate(...)
    |> $this->enricher->enrich(...)
    |> $this->mapper->map(...);

Ora il codice descrive chiaramente il percorso del dato.

Questo approccio può risultare particolarmente interessante in Magento per:

  • importazioni di catalogo;
  • integrazioni ERP e PIM;
  • normalizzazione di payload API;
  • elaborazione di configurazioni;
  • trasformazione di DTO;
  • plugin;
  • observer;
  • elaborazione di prezzi;
  • preparazione dei dati destinati a code e sistemi esterni.

Vediamo alcuni casi concreti.

Caso 1: normalizzare i dati di un prodotto proveniente da un ERP

Immaginiamo un’integrazione Magento con un ERP.

Riceviamo qualcosa del genere:

$productData = [
    'sku' => ' ABC-001 ',
    'name' => ' Prodotto Demo ',
    'price' => '12,50'
];

Un servizio di import potrebbe eseguire diverse trasformazioni:

$productData = $this->trimValues($productData);
$productData = $this->normalizeSku($productData);
$productData = $this->normalizePrice($productData);
$productData = $this->validateProduct($productData);
$productData = $this->mapMagentoAttributes($productData);

Con PHP 8.5:

$productData = $productData
    |> $this->trimValues(...)
    |> $this->normalizeSku(...)
    |> $this->normalizePrice(...)
    |> $this->validateProduct(...)
    |> $this->mapMagentoAttributes(...);

La differenza apparentemente è solo sintattica.

Dal punto di vista architetturale, però, il codice comunica molto meglio cosa sta succedendo:

Raw ERP data
    ↓
Normalize
    ↓
Validate
    ↓
Map
    ↓
Magento product data

Per integrazioni complesse questa leggibilità ha un valore concreto.

Caso 2: pipeline di import Magento

Importazioni e sincronizzazioni sono probabilmente uno dei casi migliori per utilizzare |>.

Pensiamo a una riga proveniente da CSV, API o SFTP:

$result = $row
    |> $this->normalizeRow(...)
    |> $this->resolveSku(...)
    |> $this->mapAttributes(...)
    |> $this->validateRow(...)
    |> $this->enrichProductData(...);

Ogni componente ha una responsabilità precisa:

public function normalizeRow(array $row): array;

public function resolveSku(array $row): array;

public function mapAttributes(array $row): array;

public function validateRow(array $row): array;

public function enrichProductData(array $row): array;

Questo stile incoraggia naturalmente servizi piccoli, testabili e con responsabilità ben definite.

Ed è probabilmente questo l’aspetto più interessante del pipe operator: non tanto la sintassi, quanto il tipo di design che tende a favorire.

Caso 3: configurazioni Magento

Anche i valori recuperati tramite ScopeConfigInterface spesso richiedono diverse trasformazioni.

Un esempio classico:

$value = $this->scopeConfig->getValue(
    'devlogica_module/general/allowed_skus'
);

$value = (string) $value;
$value = trim($value);
$values = explode(',', $value);
$values = array_filter($values);

Con una pipeline:

$values = $this->scopeConfig->getValue(
        'devlogica_module/general/allowed_skus'
    )
    |> strval(...)
    |> trim(...)
    |> fn(string $value) => explode(',', $value)
    |> array_filter(...);

Il flusso della trasformazione è visibile immediatamente.

Caso 4: elaborazione dei prezzi

Magento contiene numerosi punti nei quali un prezzo viene progressivamente modificato.

Per esempio, un nostro servizio potrebbe eseguire:

$price = $basePrice
    |> $this->applyCatalogRules(...)
    |> $this->applyCustomerGroupDiscount(...)
    |> $this->applyCustomAdjustment(...)
    |> $this->roundPrice(...);

La sequenza delle regole diventa estremamente evidente.

Possiamo leggere il codice quasi come una specifica funzionale:

base price
→ catalog rules
→ customer group
→ custom adjustment
→ rounding

Questo può risultare particolarmente utile quando le regole di pricing diventano numerose.

Caso 5: After Plugin

Gli after plugin Magento sono un altro candidato naturale.

Supponiamo di voler modificare progressivamente un risultato:

public function afterGetPrice(
    Product $subject,
    float $result
): float {
    return $result
        |> fn(float $price) =>
            $this->applyCustomerAdjustment($subject, $price)
        |> $this->roundPrice(...)
        |> $this->ensureMinimumPrice(...);
}

In questo caso è immediatamente visibile l’ordine nel quale vengono applicate le trasformazioni.

E, quando parliamo di prezzi, l’ordine delle operazioni può essere importante quanto le operazioni stesse.

Caso 6: preparazione di un payload verso sistemi esterni

Consideriamo un Observer che deve pubblicare un ordine verso un sistema ERP.

Potremmo avere:

$payload = $order
    |> $this->orderToPayload(...)
    |> $this->addCustomerData(...)
    |> $this->addShippingData(...)
    |> $this->removeSensitiveData(...)
    |> $this->serialize(...);

$this->publisher->publish(
    'erp.order.export',
    $payload
);

Il codice descrive perfettamente il percorso:

Magento Order
    ↓
Payload
    ↓
Customer data
    ↓
Shipping data
    ↓
Sensitive data filtering
    ↓
Serialization
    ↓
Queue

Per sistemi Magento integrati con ERP, CRM, PIM e marketplace questo pattern può diventare molto interessante.

Pipe operator e Single Responsibility Principle

C’è poi una conseguenza architetturale meno evidente.

Per funzionare bene con una pipeline, un metodo dovrebbe idealmente:

  1. ricevere un dato;
  2. eseguire una trasformazione precisa;
  3. restituire il risultato.

Per esempio:

public function normalizeSku(array $data): array
{
    // ...
    
    return $data;
}

invece di metodi che modificano continuamente stato condiviso:

public function normalizeSku(array &$data): void
{
    // ...
}

Questo tende a produrre componenti:

  • più piccoli;
  • più facilmente testabili;
  • più facilmente componibili;
  • con meno side effect;
  • con responsabilità più chiare.

Il pipe operator può quindi diventare anche uno stimolo verso un design più funzionale all’interno dei moduli Magento.

Non tutto deve diventare una pipeline

Naturalmente utilizzare |> ovunque sarebbe un errore.

Consideriamo un servizio Magento che deve:

  • caricare un ordine;
  • controllarne lo stato;
  • verificare diverse configurazioni;
  • chiamare un servizio esterno;
  • gestire differenti eccezioni;
  • salvare l’ordine;
  • pubblicare un evento.

Tentare di trasformare tutto questo in un’unica pipeline probabilmente renderebbe il codice meno leggibile.

Il pipe operator funziona particolarmente bene quando abbiamo:

A → B → C → D

dove ogni operazione trasforma chiaramente il risultato della precedente.

Funziona meno bene quando il workflow assomiglia invece a:

        → B
A → IF → C → repository
        → D → external API

In questi casi un normale Application Service rimane generalmente più chiaro.

Pipe operator e architetture Magento moderne

Negli ultimi anni lo sviluppo Magento sta progressivamente adottando pattern che separano maggiormente framework, dominio e integrazioni.

In un modulo strutturato potremmo avere:

Controller / GraphQL Resolver
        ↓
Application Service
        ↓
Transformation Pipeline
        ↓
Domain / Integration Services
        ↓
Repository / External System

Il pipe operator trova un posto naturale soprattutto nel livello di trasformazione.

Non sostituisce Dependency Injection, Service Contracts, Repository o Application Services.

È semplicemente uno strumento aggiuntivo per rappresentare in maniera più espressiva il flusso di un dato attraverso una serie di trasformazioni.

PHP 8.5 significa automaticamente Magento più veloce?

No.

Il pipe operator è principalmente una funzionalità legata a leggibilità e organizzazione del codice.

Non bisogna quindi interpretarlo come un’ottimizzazione prestazionale.

Le performance di un progetto Magento dipendono molto più frequentemente da aspetti quali:

  • query SQL;
  • utilizzo delle collection;
  • caricamento EAV;
  • cache;
  • Redis/Valkey;
  • OpenSearch;
  • indexer;
  • cron;
  • code RabbitMQ;
  • chiamate API;
  • struttura dei plugin;
  • invalidazioni della Full Page Cache.

Usare |> non renderà magicamente Magento più veloce.

Può però contribuire a rendere il codice che dobbiamo mantenere più comprensibile.

E in progetti enterprise con centinaia di migliaia di righe di codice custom questo non è un vantaggio trascurabile.

Dobbiamo riscrivere i nostri moduli Magento?

Assolutamente no.

Non avrebbe senso modificare codice stabile soltanto per adottare una nuova sintassi.

L’approccio più ragionevole è utilizzare il pipe operator quando nasce naturalmente una sequenza di trasformazioni.

Per esempio:

$payload
    |> normalize(...)
    |> validate(...)
    |> enrich(...)
    |> map(...);

Se rende il codice più semplice da capire, utilizzarlo ha senso.

Se richiede closure complesse, side effect nascosti o pipeline di quindici passaggi, probabilmente non lo ha.

Magento 2.4.9 e PHP 8.5: un’evoluzione interessante per gli sviluppatori

Il supporto di PHP 8.5 in Magento rappresenta qualcosa di più del semplice aggiornamento della versione del runtime.

Permette progressivamente all’ecosistema Magento di utilizzare funzionalità moderne del linguaggio PHP e di ridurre parte del codice cerimoniale accumulato negli anni.

Il pipe operator |> è una piccola funzionalità, ma si adatta sorprendentemente bene a diversi problemi tipici dello sviluppo Magento:

import
normalize
validate
enrich
transform
export

Non cambierà il modo in cui funziona Magento.

Può però cambiare, in alcuni punti, il modo in cui raccontiamo attraverso il codice ciò che Magento sta facendo.

Ed è spesso proprio questa la differenza tra codice semplicemente funzionante e codice che rimane comprensibile anche diversi anni dopo.

Hai un progetto Magento da modernizzare?

In Devlogica lavoriamo su progetti Magento Open Source e Adobe Commerce, dallo sviluppo di moduli custom alle integrazioni con ERP, CRM, PIM e marketplace, fino all’ottimizzazione di performance e architetture Magento esistenti.

Se stai pianificando un aggiornamento a una versione recente di Magento, una migrazione dello stack PHP o vuoi ridurre il debito tecnico di un progetto esistente, possiamo analizzare insieme architettura, compatibilità e strategia di aggiornamento.