webgriffe / sylius-fixtures-plugin
Sylius fixtures with fixed prices, per locale translations, stock, shipping categories, plus a complete demo store.
Package info
github.com/webgriffe/sylius-fixtures-plugin
Type:sylius-plugin
pkg:composer/webgriffe/sylius-fixtures-plugin
Requires
- php: ^8.3
- sylius/sylius: ^2.3
- symfony/config: ^6.4 || ^7.4
- symfony/dependency-injection: ^6.4 || ^7.4
- symfony/http-kernel: ^6.4 || ^7.4
- webmozart/assert: ^1.11
Requires (Dev)
- behat/behat: ^3.16
- dbrekelmans/bdi: ^1.4
- dmore/behat-chrome-extension: ^1.4
- dmore/chrome-mink-driver: ^2.9
- friends-of-behat/mink: ^1.11
- friends-of-behat/mink-browserkit-driver: ^1.6
- friends-of-behat/mink-debug-extension: ^2.1
- friends-of-behat/mink-extension: ^2.7
- friends-of-behat/page-object-extension: ^0.3
- friends-of-behat/suite-settings-extension: ^1.1
- friends-of-behat/symfony-extension: ^2.6
- friends-of-behat/variadic-extension: ^1.6
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^1.12
- phpstan/phpstan-doctrine: ^1.3
- phpstan/phpstan-webmozart-assert: ^1.2
- phpunit/phpunit: ^10.5
- sylius-labs/coding-standard: ^4.4
- sylius-labs/suite-tags-extension: ~0.2
- sylius/sylius-rector: ^2.0
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4
- symfony/debug-bundle: ^6.4 || ^7.4
- symfony/dotenv: ^6.4 || ^7.4
- symfony/http-client: ^6.4 || ^7.4
- symfony/intl: ^6.4 || ^7.4
- symfony/runtime: ^6.4 || ^7.4
- symfony/web-profiler-bundle: ^6.4 || ^7.4
- symfony/webpack-encore-bundle: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-10-13 14:10:43 UTC
README
Sylius fixtures that can actually describe a real catalog: fixed prices, per locale translations, stock, shipping categories and configurable products. It also ships a complete demo store (wines, glassware and food and wine experiences) ready to be loaded on any Sylius project.
Installation
composer require webgriffe/sylius-fixtures-plugin --dev
Register the plugin in config/bundles.php:
Webgriffe\SyliusFixturesPlugin\WebgriffeSyliusFixturesPlugin::class => ['dev' => true, 'test' => true],
What it adds to the Sylius fixtures
Products
| Option | Description |
|---|---|
price |
Price of every variant, in the currency unit (12.50), instead of the random one generated by Sylius |
variant_prices |
Price per variant, keyed by product option value: numero_calici_2: 49.00. Several options are joined by a + (esperienza_2_giorni+partecipanti_4: 800.00), their order does not matter. price is the fallback |
minimum_price / minimum_price_ratio |
Floor the catalog promotions cannot go below, either as an amount or as a share of the price of each variant (0.75 keeps at most a 25% discount) |
original_price |
Original price, to show a discount |
on_hand |
Stock of every variant |
width, height, depth, weight |
Size of every variant, used by the weight and volume based shipping calculators |
variant_dimensions |
Size per variant, keyed like variant_prices: a pack of 8 glasses does not weigh what a pack of 2 does |
shipping_category |
Shipping category code, assigned to every variant. Sylius never sets it, so any shipping method bound to a category would be discarded at checkout |
translations |
name, slug, short_description, description, meta_keywords and meta_description per locale. Sylius writes the same translation in every locale |
variant_name |
Name per locale of the variant of a product without options (Bottiglia 0,75 L), the name of the product by default |
The slug of each locale is generated from the name of that locale when the translation does not state one: Sylius generates it once, from the first name it reads, so a product translated in several languages would answer on the same url everywhere.
Every variant gets a name in every locale: after its option values when it has some, after variant_name or
the product otherwise. Sylius names a variant once, in a single locale and with an empty name when it has no
options, so the inventory of the administration flags all the others as missing translation.
variant_selection_method is a native Sylius option, worth choosing on the shape of the options of a
configurable product: the demo store uses choice for the glassware, a single option with a few packs that
read best as a list with their own price, and match for the experiences, two options whose nine
combinations read best as two selects.
sylius_fixtures: suites: my_suite: fixtures: product: options: custom: - code: 'chianti_docg' name: 'Chianti DOCG' price: 9.50 minimum_price_ratio: 0.75 on_hand: 180 tracked: true shipping_category: 'bottles' weight: 1.3 width: 8.0 height: 32.0 depth: 8.0 translations: it_IT: name: 'Chianti DOCG' short_description: 'Sangiovese fresco e sapido.' en_US: name: 'Chianti DOCG' short_description: 'Fresh and savoury Sangiovese.'
Orders
| Option | Description |
|---|---|
state |
completed (default) or cancelled, to show cancelled orders in the admin as well |
cancelled_orders: name: order options: amount: 3 channel: 'ecommerce' state: 'cancelled'
The
paymentsfixture completes the payments without checking whether the transition is allowed, so declare it before the fulfilled and the cancelled orders, or it will try to pay an order that cannot be paid.
Payment methods
| Option | Description |
|---|---|
translations |
name, description and instructions per locale |
The instructions are the text the customer reads after placing the order, the place for the bank details of an offline transfer.
Product association types
| Option | Description |
|---|---|
translations |
Name of the association type per locale |
The
similar_product_associationfixture of Sylius creates its own type named "Similar products", hard coded and identical in every locale. Declareproduct_association_typeandproduct_associationinstead, as the demo store does: it also lets you choose the associations rather than picking them at random.
Product attributes
The name of an attribute is translated by Sylius itself since 2.3, with its translations option:
- name: 'Regione' code: 'region' type: 'text' translations: it_IT: name: 'Regione' en_US: name: 'Region'
The values of the attributes are translated from the product, where a value is either a scalar, written in every locale as Sylius does, or a map of locale to value:
product_attributes: winery: 'Ruffino' region: it_IT: 'Toscana' en_US: 'Tuscany' fr_FR: 'Toscane'
A map is recognized as translated when all its keys are locale codes: any other array is a value of its own,
like the list of choices of a select attribute. The dates of the translated values are written as strings,
the plugin turns them into dates as Sylius does for the others.
The values of a select attribute are the keys of its choices, always as a list, multiple or not:
- name: 'Abbinamenti' code: 'pairings' type: 'select' translatable: false configuration: multiple: true min: 1 max: 4 choices: red_meat: it_IT: 'Carni rosse' en_US: 'Red meat' cheese: it_IT: 'Formaggi stagionati' en_US: 'Aged cheeses' product_attributes: pairings: ['red_meat', 'cheese']
The demo store holds an attribute of every type, translatable and not, so every way Sylius stores and shows an attribute can be seen:
| Type | Translatable | Not translatable |
|---|---|---|
text |
region of the wine | article number of the glasses |
textarea |
tasting notes | EAN codes of the packs |
checkbox |
guide in your language | dishwasher safe |
integer |
critics score of each market | vintage |
float |
serving temperature, in °F for the American market | total acidity |
percent |
deposit on booking | alcohol content |
date |
next departure, market by market | bottling date |
datetime |
online tasting, one a market | bookings close |
select single |
best for, following the habits of each market | closure |
select multiple |
product badges | food pairings |
Product options
The name of an option and its values are translated by Sylius itself since 2.3, with its translations
option. Keep the values as well, with the value of the default locale: Sylius requires at least one.
product_option: options: custom: - name: 'Numero di calici' code: 'number_of_glasses' translations: it_IT: name: 'Numero di calici' values: two_glasses: '2 calici' en_US: name: 'Number of glasses' values: two_glasses: '2 glasses' values: two_glasses: '2 calici'
The variants of a configurable product are named after their option values in every locale: Sylius names them once, in whatever locale the entity happens to be in, so a configurable product would end up with its variants translated in a single language.
Taxons
| Option | Description |
|---|---|
images |
Images of the taxon, at any level of the tree. Sylius ignores them completely in its own fixtures |
taxon: options: custom: category: code: 'category' images: - { path: '@WebgriffeSyliusFixturesPlugin/fixtures/images/wines/enoteca_1.webp', type: 'main' } children: - code: 'red_wines' images: - { path: '@WebgriffeSyliusFixturesPlugin/fixtures/images/wines/vino_rosso_1.webp', type: 'main' }
A taxon declared in several fixtures files gets its images added once per declaration: keep them in a single file, as the demo store does with its root category.
Shipping methods
| Option | Description |
|---|---|
category_requirement |
match_any (default in Sylius), match_all or match_none |
translations |
name and description per locale: Sylius writes the same ones in every locale |
min_delivery_time_days, max_delivery_time_days |
Estimated delivery time, shown at the checkout |
rules |
type and configuration of every rule the method must satisfy, which Sylius cannot declare at all |
shipping_method: options: custom: standard: code: 'standard' name: 'Standard courier' zone: 'IT' category: 'bottles' category_requirement: 'match_all' rules: - type: 'total_weight_less_than_or_equal' configuration: weight: 30 - type: 'order_total_greater_than_or_equal' configuration: ecommerce: amount: 5000
The rules of Sylius are total_weight_less_than_or_equal and total_weight_greater_than_or_equal, configured
with a weight in the unit of the variants, and order_total_less_than_or_equal and
order_total_greater_than_or_equal, configured with an amount per channel. In the demo store the couriers
carry up to 30 kg, the shockproof boxes up to 20 kg, and the heavier orders travel on a pallet.
Mind the gaps: a cart that no method accepts cannot be checked out, and it cannot be created by the
orderfixture either.
Orders
The demo orders are created in the default locale of the channel, their addresses are generated with a faker localized on the country of the address instead of the American one used by Sylius, and every shipped shipment carries a tracking code, which Sylius never writes. They are shipped with one of the methods the customer could have chosen, while Sylius picks any method of the channel, whatever its zone, its categories and its rules. Every address also carries its province: the code of a real province where the country has them (Italy in the demo store), the name of a region as free text everywhere else.
The regions are a table of the plugin, not faker data: half of its locales have no region at all, and some fall back to the American states, so a Bulgarian address would end up in Vermont.
Sylius picks a random locale and then loads the products joining only the translation of that locale: reading the product name in another locale creates an empty translation and breaks the flush on a multi locale channel.
Admin users
| Option | Description |
|---|---|
administration_access |
false for an administrator without access to the panel: Sylius always grants it, even to a user of the API |
admin_user: options: custom: - email: 'api@example.com' username: 'api' api: true administration_access: false
The two access levels of Sylius 2.3 are roles: ROLE_ADMINISTRATION_ACCESS for the panel, ROLE_API_ACCESS
for the API. The demo store has one administrator for each of them, admin and api.
Customers
| Option | Description |
|---|---|
subscribed_to_newsletter |
Subscribes the customer to the newsletter, which the Sylius fixtures never set |
shop_user: options: custom: - email: 'cliente@webgriffe.com' first_name: 'Mario' last_name: 'Rossi' password: 'webgriffe' subscribed_to_newsletter: true
Promotions
| Fixture | Option | Description |
|---|---|---|
catalog_promotion |
translations |
label and description per locale |
promotion |
translations |
The label per locale, the text the customer reads on the cart |
promotion |
track_usage |
false to leave the usage counter of the promotion untouched by the orders (Sylius 2.3) |
promotion |
coupons.*.track_usage |
The same for a single coupon |
catalog_promotion: options: custom: white_wines: code: 'white_wines' name: 'White wine week' translations: it_IT: label: 'Settimana dei bianchi: 10% di sconto' description: 'Tutti i bianchi in catalogo scontati del 10%.' en_US: label: 'White wine week: 10% off' description: 'Every white in the catalogue is 10% off.'
The description of a cart promotion is not translatable in Sylius: it lives on the promotion itself, next to its name, and it is only read in the administration.
A promotion or a coupon that does not track its usage is never stopped by its usage limit, and its orders are
left out of its statistics: the demo store uses it for the STAFF25 coupon of the employees.
promotion: options: custom: staff_coupon: code: 'staff' coupon_based: true track_usage: false coupons: - code: 'STAFF25' track_usage: false
Sylius dates the usage of a coupon from the moment its tracking is turned on (
track_usage_since), which has no option: it is written by Sylius itself, and only when the tracking is switched on again.
Sylius makes a cart promotion exclusive at random when the option is missing: declare
exclusiveon every promotion, an exclusive one applied to the cart stops all the others.
Provinces
The province fixture creates the provinces of a country with their abbreviation, which the geographical
fixture of Sylius leaves empty: it only takes a code and a name. Declare it after geographical, the countries
have to exist.
province: options: countries: IT: IT-BO: name: 'Bologna' abbreviation: 'BO'
Exchange rates
The exchange_rate fixture creates the exchange rates between the currencies of the shop, which Sylius has no
fixture for: without them a price shown in another currency is the same number as in the base currency.
Declare it after currency, the currencies have to exist.
exchange_rate: options: exchange_rates: - source_currency: 'EUR' target_currency: 'GBP' ratio: 0.85373
Sylius converts the prices only to show them: the orders are still placed and paid in the base currency.
Price history (Omnibus)
The price_history fixture writes the past prices of a product, so the shop can show the lowest price of the
last 30 days required by the Omnibus directive. Sylius only logs the price of the moment the fixtures run, so
without it every product looks like it has always had its current price and the line never shows anything
useful.
price_history: options: products: chianti_docg: entries: - { days_ago: 50, price: 15.90 } - { days_ago: 26, price: 13.90 }
Do not list the price of today: the fixture logs it by itself as the last entry, and Sylius excludes exactly that entry from the computation. It then runs the Sylius processor, so the lowest price is computed the same way it would be in production.
The lowest price is stored only when the variant is discounted, that is either:
- a catalog promotion applies to it, and the price is recomputed on every run (remember to consume the
messenger
maintransport, the promotions are applied asynchronously); - or the product declares an
original_pricehigher than its price, a markdown made by hand.
Never combine the two on the same product: the catalog promotion recomputes the price from the original one, overwriting the markdown.
Shop
Importing config/config.yaml of the plugin also dresses the shop as the demo wine shop, through twig hooks:
- the logo of Sylius is replaced by the one of the wine shop in the header, the footer, the checkout and the error pages, and the footer credits the company of the channel;
- the homepage banner, a clothing photo, becomes a vineyard at sunset with the name of the channel and a
tagline (
webgriffe_sylius_fixtures.homepage.banner_tagline); - the new collection, three more clothing photos, becomes the categories of the menu of the channel, with their image, name and link;
- the top bar stops promising 20% off for the newsletter, which the demo store does not give, and announces
its free shipping in Italy instead (
sylius.top_bar).
The texts are translated in it, en and fr. The banner is an asset of the bundle: run bin/console assets:install
after installing the plugin.
The demo store
Import the fixtures file of the plugin to get the webgriffe suite:
# config/packages/sylius_fixtures.yaml imports: - { resource: '@WebgriffeSyliusFixturesPlugin/Resources/config/app/fixtures.yaml' }
bin/console sylius:fixtures:load webgriffe -n
# catalog promotions are applied asynchronously
bin/console messenger:consume main --time-limit=60
Every zone of the demo store has a single scope,
taxorshipping: Sylius taxes an address with one zone of the tax scope, picking whichever the database returns first when two of them match, so Italy and the European Union, or the United Kingdom and the extra EU shipping zone, must never share a scope.
The variants of the demo store are tracked, and the demo orders decrease their inventory: their stock is therefore generous (200 units and up) so that a random set of orders can never exhaust a variant and break the loading. Lower it in your own suite if you need to demo the out of stock behaviour.
It creates an Italian wine shop: the ecommerce channel in three locales (it_IT, en_US, fr_FR) with prices in
euro, also shown in pounds and Swiss francs at the ECB rates of 1 October 2026, shipping to the European Union,
the United Kingdom and Switzerland, 27 wines, 5 configurable sets of glasses, 4 food and wine experiences (nothing to
ship, VAT 10%), promotions, 20 orders, 40 reviews and 20 customers.
The pictures are public domain (CC0) photos, their origin is tracked in
src/Resources/fixtures/images/CREDITS.md.
Contributing
composer install
composer suite # ecs + phpstan + phpunit
License
This plugin is under the MIT license. See the complete license in the LICENSE file.