Apparence
Les pipelines
Un pipeline transforme une valeur en la faisant traverser une chaîne de maillons, chacun contribué de l'extérieur, dans un ordre maîtrisé. C'est la mécanique du prix et des totaux — mais le concept est générique.
Le problème
Le prix d'un produit n'est pas une donnée figée : une promotion le baisse, une grille B2B le recalcule, la taxe s'applique à la fin… Ces transformations viennent de packages différents, qui ne se connaissent pas, et leur ordre compte. Le cœur ne doit connaître aucune d'elles.
Le principe
Une valeur (dans un contexte) traverse des maillons triés par priorité. Chaque maillon reçoit le contexte, le transforme, puis passe la main au suivant — exactement comme un middleware HTTP.
Écrire un maillon
Un maillon de prix implémente PriceModifier. Il mute le prix HT puis rend la main :
php
use Slab\Framework\Core\Pricing\Contracts\PriceModifier;
use Slab\Framework\Core\Pricing\PriceContext;
use Closure;
final class FideliteePriceModifier implements PriceModifier
{
public function handle(PriceContext $context, Closure $next): PriceContext
{
if (/* le client a un avantage fidélité */) {
$context->price = $context->price->multipliedBy('0.95', RoundingMode::HALF_UP);
$context->trace[] = ['fidelite', '-5%'];
}
return $next($context); // toujours passer la main
}
}Le PriceContext porte le produit, la quantité, le client, le price (un Brick\Money\Money, muté en chaîne) et une trace d'audit.
L'enregistrer avec une priorité
Dans le boot() de votre provider — l'ordre croissant détermine l'enchaînement :
php
use Slab\Framework\Core\Pricing\Contracts\PriceModifierRegistry;
$this->app->make(PriceModifierRegistry::class)->add(FideliteePriceModifier::class, priority: 60);Priorités des maillons officiels : la promotion à 50, la grille B2B à 10. Un nombre plus petit s'applique plus tôt.
Taxe & devise : toujours en fin de chaîne
Les maillons raisonnent en HT. La taxe (puis la devise) est un maillon cœur appliqué après tous les autres — vous n'avez jamais à vous en occuper.
Le second pipeline : les totaux
Le panier et la commande affichent des lignes de totaux (sous-total, livraison, remise…). Même mécanique, avec TotalsModifier qui contribue des lignes au lieu de muter une valeur :
php
use Slab\Framework\Core\Pricing\Contracts\TotalsModifier;
use Slab\Framework\Core\Pricing\{TotalsContext, TotalsLine};
final class EcoPartTotalsModifier implements TotalsModifier
{
/** @return list<TotalsLine> */
public function lines(TotalsContext $context): array
{
return [ new TotalsLine('eco_part', __('Éco-participation'), $exclTax, $inclTax) ];
}
}php
$this->app->make(TotalsModifierRegistry::class)->add(EcoPartTotalsModifier::class, priority: 35);Le cœur agrège : ligne « produits » + lignes contribuées = ligne « total », en sommant exactement en Money. Au passage en commande, ces lignes sont figées (relecture sans recalcul, immuable).
Réordonner sans toucher au code
Le projet final réécrit les priorités en config, sans modifier les packages :
php
// config/pricing.php
return [
'priorities' => [ App\Pricing\FideliteePriceModifier::class => 40 ],
'totals_priorities' => [ /* … */ ],
];Remplacer l'orchestration entière
Ajouter un maillon couvre l'immense majorité des cas. Si vous devez changer l'orchestration elle-même — l'algorithme qui assemble le prix, les totaux ou le placement de commande — chacun de ces orchestrateurs est résolu derrière un contrat substituable, comme n'importe quel service :
| Contrat | Rôle | Défaut |
|---|---|---|
PricePipeline | calcule le prix décomposé d'un produit | DefaultPricePipeline |
TotalsBuilder | assemble les lignes de totaux (panier / commande) | DefaultTotalsBuilder |
OrderPlacementPipeline | place une commande à partir d'un panier | DefaultOrderPlacementPipeline |
php
use Slab\Framework\Core\Pricing\Contracts\PricePipeline;
$this->app->bind(PricePipeline::class, MonPricePipeline::class);C'est le même geste que pour n'importe quel service du cœur : un plugin le fait pour un comportement réutilisable sur plusieurs projets, la couche applicative du skeleton pour du sur-mesure ponctuel. Pour le placement, on réagit d'ordinaire via OrderPlaced ; ne remplacez le pipeline que pour changer le déroulé lui-même.
Aller plus loin
- Le détail tarifaire (taxe, devise, commande figée) : domaine Pricing & taxe.
- Les contrats exacts : référence des points d'extension.