Search by

phil / commerce-product-template

phil

Product templates for PHP: axes with ordered values, swatches and supplier names, prices per value with supplements, faces and print zones in millimetres, combinations excluded, a SKU pattern checked for collisions, and designs planned on templates as one product per language and combination

0.1.0-alpha 2026-11-11 06:11 UTC

This package is auto-updated.

Last update: 2026-11-11 13:29:52 UTC


README

One template and one design give every product of a catalogue: each SKU, its price, its channels and its artworks.

phil/commerce-product-template describes a product made to order on a blank. A template is the blank: its axes — a size, a colour, a version — with their values in order, each with its code, its label in several languages, its swatch, the name the supplier gives it and the photo of the blank on each face; the price of each combination, from a base, a price per value of one axis and supplements on any axis; the faces and their print zones in millimetres; the combinations the supplier does not make; and the pattern of the SKU, {design}{lang}{version}{size}{colour} or any other. A design is what is printed: its code, its language versions, its artworks per face and zone, the templates it is made on and the values it allows. The planner turns a design on a template into one product per language and combination, refuses a pattern that would give two products the same SKU, and gives the same plan for the same input, so a catalogue regenerated from it is updated rather than duplicated.

It is for a shop that sells printed goods in many sizes and colours from a handful of blanks: shirts, hoodies, mugs, posters. A shirt is the example the documentation follows; nothing in the library knows what a shirt is.

It is a plain PHP library with phil/money for its prices and nothing else. The Symfony bundle phil/commerce-product-template-bundle stores templates and designs, generates the products of a catalogue from the plan, composes their mockups and declares their options.

Status

Part State
Axes and values: codes, labels by locale, swatches, supplier names, photos per face, channels Implemented and tested
Prices: base, a price per value of one axis, supplements on any axis Implemented and tested
Faces and print zones in millimetres, resolution, rectangle on the photos Implemented and tested
Combinations excluded by axis values Implemented and tested
SKU patterns, glued or separated, with the collision check Implemented and tested
Designs: language versions, artworks by face, zone, language and condition, templates and values allowed Implemented and tested
Planning: products, prices, channels, artworks, names and attributes Implemented and tested
Arrays for storage, versioned and checked on reading Implemented and tested

Documentation

Document What it covers
Installation Requirements and installing.
Templates Template, Axis, AxisValue, Label, Swatch, Face, PrintZone, PhotoBox, Pricing, Exclusion.
SKU patterns Placeholders, literals, the collision check.
Designs Design, LanguageVersion, Artwork: languages, artworks and what a design allows.
Planning VariantPlanner, Variant, PlacedArtwork: what a plan holds and refuses.
Storage TemplateArray and DesignArray: the arrays, their version, what reading refuses.
Extension points What an application chooses.
Exceptions ProductTemplateException, InvalidDefinition, PlanRefused.
Testing The library's tests, fuzz targets and benchmark.
Reference Every public type of the library.

Quick start

composer require phil/commerce-product-template
use Phil\CommerceProductTemplate\{Artwork, Axis, AxisValue, Design, Exclusion, Face, Label, LanguageVersion};
use Phil\CommerceProductTemplate\{Pricing, PrintZone, SkuPattern, Swatch, Template, VariantPlanner};
use Phil\Money\{Currency, Money};

$cad = static fn (int $cents): Money => Money::of($cents, Currency::of('CAD'));

$shirt = Template::of('unisex', Label::of(['en' => 'Unisex shirt', 'fr' => 'T-shirt unisexe']),
    SkuPattern::parse('{design}{lang}{version}{size}{colour}'),
    Pricing::of($cad(2_500))->withPrice('size', '2X', $cad(2_800))->withSupplement('version', 'C', $cad(1_000)),
    [
        Axis::of('version', Label::of(['en' => 'Version']),
            AxisValue::of('O', Label::of(['en' => 'Regular'])),
            AxisValue::of('C', Label::of(['en' => 'Convention']), channels: ['convention'])),
        Axis::of('size', Label::of(['en' => 'Size']),
            AxisValue::of('M', Label::of(['en' => 'M'])), AxisValue::of('2X', Label::of(['en' => '2XL']))),
        Axis::of('colour', Label::of(['en' => 'Colour']),
            AxisValue::of('BK', Label::of(['en' => 'Black']), Swatch::colour('#1A1A1A'), 'black', ['front' => 'blanks/front-bk.jpg']),
            AxisValue::of('PK', Label::of(['en' => 'Pink']), Swatch::colour('#F4A7C0'), 'azalea')),
    ],
    [Face::of('front', Label::of(['en' => 'Front']), PrintZone::of('chest', 50, 60, 300, 400))],
    [Exclusion::of(['colour' => ['PK'], 'size' => ['2X']])],
);

$gnome = Design::of('NAQ001', Label::of(['fr' => 'Le gnome', 'en' => 'The gnome']),
    [LanguageVersion::of('F', 'fr'), LanguageVersion::of('E', 'en')],
    [Artwork::of('front', 'art/naq001-front.png'), Artwork::of('front', 'art/naq001-front-en.png', language: 'E')]);

foreach (new VariantPlanner()->plan($shirt, $gnome) as $variant) {
    echo $variant->sku, ' ', $variant->price->toDecimal(), ' ', $variant->name('en'), "\n";
}
// NAQ001FOMBK 25.00 Le gnome – T-shirt unisexe (Regular, M, Black)
// ...

Requirements

PHP 8.5 or later with ext-mbstring, and phil/money.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.