/fixtures — Customiser les fixtures Sylius
Tu aides à customiser les fixtures Sylius : soit pour modifier la suite default (currencies, channels, shipping/payment methods, etc.) via config/packages/sylius_fixtures.yaml, soit pour étendre un ExampleFactory + Fixture afin d'exposer un champ ajouté sur une entité customisée. Les fixtures sont des objets PHP qui implémentent Sylius\Bundle\FixturesBundle\Fixture\FixtureInterface et sont taguées sylius_fixtures.fixture ; elles servent à initialiser ou modifier l'état de l'application (DB, fichiers, événements) — usage typique : seed dev/QA, demo data, init prod.
Référence officielle : docs.sylius.com/the-customization-guide/customizing-fixtures.
Détection préalable (obligatoire)
- Lire
composer.jsonà la racine. - Vérifier
sylius/syliusdans les dépendances.- Présent → OK.
- Absent → « Ce skill cible Sylius (customisation des fixtures via
sylius_fixtures.suites.*). Je ne trouve passylius/sylius. On continue quand même ? »
- Si la demande consiste à ajouter un champ à un modèle natif (ex.
deliveryConditionssurShippingMethod) avant de l'exposer en fixture → basculer d'abord sur/sylius:model(ou/sylius:translation-entitysi traduisible) pour créer le champ + migration, puis revenir ici pour étendre la factory et la fixture. - Si la demande est « charger des fixtures de test pour un test fonctionnel/E2E » (et pas customiser les fixtures elles-mêmes) → c'est l'utilisation de fixtures, pas leur customisation :
sylius:fixtures:load <suite>suffit, pas besoin de cette skill.
Règles fondamentales
- Suite
default= la suite chargée parsylius:fixtures:loadsans argument. Les autres suites (dev,test,demo…) doivent être appelées explicitement :sylius:fixtures:load demo. Utile pour séparer seed prod (default) et seed QA/demo. priority: plus le nombre est petit, plus la fixture s'exécute tard. C'est l'inverse de l'intuition classique. Ordre par défaut implicite via dépendances déclarées dans les fixtures vendor (currencies avant channels, channels avant shipping methods, etc.) — ne pas y toucher sans raison forte.- Clé
custom: ajoute des entrées sans remplacer les fixtures existantes par défaut de Sylius. Si tu veux ne charger que tes entrées, il faut désactiver la suite par défaut et ne déclarer que les tiennes — sinon les channels/products fashion store coexisteront avec les tiens. - Le tag
sylius_fixtures.fixtureest obligatoire : sans lui, la classe est instanciée mais jamais découverte par leFixtureRegistry. Vérifier viabin/console debug:container --tag=sylius_fixtures.fixtureque ta fixture apparaît bien. - Constructeur de l'
ExampleFactory: depuis Sylius 2.0.8, les arguments sontprotected→ on peut juste appelerparent::__construct(...)et n'override que ce qui change. Sur Sylius < 2.0.8, il faut redéclarer le constructeur complet avec tous les arguments du parent (sinon l'autowiring échoue ou des dépendances manquent). - Override du service Sylius natif : on redéfinit le service avec le même id (
sylius.fixture.example_factory.shipping_method) — pas un alias, pas un nouveau service. Le tagpublic: trueest requis pour que leFixtureBuilderpuisse le résoudre via le container. - Désactiver l'autowiring sur
src/Fixture/: sans exclusion, Symfony auto-configure tes fixtures en plus des services manuellement déclarés →Multiple definitions for the same service id. La règle d'exclusion vit dansconfig/services.yaml:App\: resource: '../src/*' exclude: '../src/{Entity,Fixture,Migrations,Tests,Kernel.php}' - Translatable + fixture : si le champ custom est sur une
*Translation, la factory doit settercurrentLocale+fallbackLocalepour chaque locale avant d'écrire la valeur — sinon Doctrine persiste sur une translation aléatoire.
Déroulement
1 — Cadrer le besoin
Demander (ou confirmer si déjà fourni) :
- Type d'opération :
- A — modifier la suite par défaut (currencies, locales, channels, shipping/payment methods, products…) sans toucher au code PHP.
- B — exposer un champ custom d'un modèle étendu dans une fixture (nécessite Factory + Fixture + service config).
- C — créer une suite custom entièrement nouvelle (ex.
dev,demo,test) qui charge un sous-ensemble ciblé.
- Pour le cas B : nom du modèle, nom du champ, type (scalaire / translatable / relation), valeur par défaut.
2 — Lister les fixtures dispo (toujours utile)
bin/console sylius:fixtures:list
Donne la liste des fixtures réellement enregistrées dans le container (vendor + custom). C'est la source de vérité pour savoir quel key utiliser sous sylius_fixtures.suites.<suite>.fixtures.<key>. Si ta fixture custom n'apparaît pas → tag sylius_fixtures.fixture manquant ou autowiring qui shadow.
3A — Modifier la suite par défaut (cas le plus fréquent)
Créer/éditer config/packages/sylius_fixtures.yaml :
sylius_fixtures:
suites:
default: # Suite chargée par `sylius:fixtures:load`
fixtures:
currency:
options:
currencies: ['CZK', 'HUF']
channel:
options:
custom:
cz_web_store:
name: "CZ Web Store"
code: "CZ_WEB"
locales:
- "%locale%"
currencies:
- "CZK"
enabled: true
hostname: "localhost"
shipping_method:
options:
custom:
ups_eu:
code: "ups_eu"
name: "UPS_eu"
enabled: true
channels:
- "CZ_WEB"
payment_method:
options:
custom:
bank_transfer:
code: "bank_transfer_eu"
name: "Bank transfer_eu"
channels:
- "CZ_WEB"
enabled: true
Points de vigilance :
channels:dansshipping_method/payment_methoddoit référencer uncodede channel existant (vendorWEB-US,WEB-EU, ou ton customCZ_WEB). Une typo ne lève pas d'erreur explicite — la méthode est créée sans channel.currencies:souschanneldoit lister des codes ISO déjà déclarés sous la fixturecurrency. Ordre des fixtures (currency avant channel) est garanti par les priorités vendor — ne pas inverser.%locale%est le paramètre Symfony app → résout enen_USpar défaut, à overrider viaframework.default_locale.
3B — Exposer un champ custom (modèle étendu)
Préalable : le champ doit déjà exister sur l'entité (ex. ShippingMethod::deliveryConditions ajouté via /sylius:model).
Étendre l'ExampleFactory
src/Fixture/Factory/ShippingMethodExampleFactory.php :
<?php
declare(strict_types=1);
namespace App\Fixture\Factory;
use Sylius\Bundle\CoreBundle\Fixture\Factory\ShippingMethodExampleFactory as BaseShippingMethodExampleFactory;
use Sylius\Component\Core\Model\ShippingMethodInterface;
use Sylius\Component\Locale\Model\LocaleInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class ShippingMethodExampleFactory extends BaseShippingMethodExampleFactory
{
public function create(array $options = []): ShippingMethodInterface
{
$shippingMethod = parent::create($options);
if (!isset($options['deliveryConditions'])) {
return $shippingMethod;
}
foreach ($this->getLocalesFromRepository() as $localeCode) {
$shippingMethod->setCurrentLocale($localeCode);
$shippingMethod->setFallbackLocale($localeCode);
$shippingMethod->setDeliveryConditions($options['deliveryConditions']);
}
return $shippingMethod;
}
protected function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver
->setDefault('deliveryConditions', null)
->setAllowedTypes('deliveryConditions', ['null', 'string'])
;
}
private function getLocalesFromRepository(): iterable
{
/** @var LocaleInterface[] $locales */
$locales = $this->localeRepository->findAll();
foreach ($locales as $locale) {
yield $locale->getCode();
}
}
}
Étendre la Fixture
src/Fixture/ShippingMethodFixture.php :
<?php
declare(strict_types=1);
namespace App\Fixture;
use Sylius\Bundle\CoreBundle\Fixture\ShippingMethodFixture as BaseShippingMethodFixture;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
final class ShippingMethodFixture extends BaseShippingMethodFixture
{
protected function configureResourceNode(ArrayNodeDefinition $resourceNode): void
{
parent::configureResourceNode($resourceNode);
$resourceNode
->children()
->scalarNode('deliveryConditions')->end()
;
}
}
configureResourceNode étend le schéma de configuration YAML : sans cette étape, déclarer deliveryConditions: dans sylius_fixtures.yaml lève Unrecognized option. Ne pas oublier parent::configureResourceNode() — sinon tu perds tous les nœuds vendor (code, name, enabled, channels…).
Déclarer les services
config/services.yaml :
services:
sylius.fixture.example_factory.shipping_method:
class: App\Fixture\Factory\ShippingMethodExampleFactory
arguments:
- "@sylius.factory.shipping_method"
- "@sylius.repository.zone"
- "@sylius.repository.shipping_category"
- "@sylius.repository.locale"
- "@sylius.repository.channel"
- "@sylius.repository.tax_category"
public: true
sylius.fixture.shipping_method:
class: App\Fixture\ShippingMethodFixture
arguments:
- "@sylius.manager.shipping_method"
- "@sylius.fixture.example_factory.shipping_method"
tags:
- { name: sylius_fixtures.fixture }
Sources d'arguments à respecter (sinon le constructeur du parent échoue) :
ExampleFactory: copier la signature du constructeur du parent vendor (vendor/sylius/sylius/src/Sylius/Bundle/CoreBundle/Fixture/Factory/<Model>ExampleFactory.php). L'ordre compte.Fixture: généralement[manager, exampleFactory], mais certains fixtures vendor injectent en plus un repository ou un EventDispatcher → vérifiervendor/sylius/sylius/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures.xml.
Exclusion d'autowiring requise pour src/Fixture/ (cf. règles fondamentales).
Utiliser le champ dans le YAML
sylius_fixtures:
suites:
default:
fixtures:
shipping_method:
options:
custom:
geis:
code: "geis"
name: "Geis"
enabled: true
channels:
- "CZ_WEB"
deliveryConditions: "3-5 days"
3C — Suite custom dédiée
Pour un seed alternatif (demo, tests E2E, environnement preview) :
sylius_fixtures:
suites:
demo:
listeners:
orm_purger: ~ # Purge la DB avant chargement
fixtures:
currency:
options:
currencies: ['EUR']
channel:
options:
custom:
demo_store:
code: "DEMO"
name: "Demo Store"
hostname: "demo.localhost"
locales: ["en_US"]
currencies: ["EUR"]
enabled: true
Chargement : bin/console sylius:fixtures:load demo. Sans orm_purger, la suite est cumulative — utile pour empiler des seeds, dangereux si tu attendais un état propre.
4 — Charger et vérifier
bin/console cache:clear
bin/console sylius:fixtures:list # ta fixture custom doit apparaître
bin/console sylius:fixtures:load # suite "default"
# ou
bin/console sylius:fixtures:load demo
Vérifs post-load :
- Channel créé :
bin/console doctrine:query:sql "SELECT code, name FROM sylius_channel". - Shipping/payment methods rattachés : check
sylius_shipping_method_channels/sylius_payment_method_channels. - Champ custom persisté (cas B) :
SELECT id, code, delivery_conditions FROM sylius_shipping_method_translation.
5 — Clôture
Afficher :
- Fichiers créés/modifiés :
config/packages/sylius_fixtures.yaml,config/services.yaml, éventuellementsrc/Fixture/Factory/<Model>ExampleFactory.php+src/Fixture/<Model>Fixture.php. - Commande de chargement à jouer (
sylius:fixtures:load [suite]). - Ce qui reste : éventuelle suite
testpour les tests E2E/Behat, hooks de purge, exposition admin du champ custom (→/sylius:form).
Pièges fréquents
- Fixture custom invisible dans
sylius:fixtures:list: tagsylius_fixtures.fixturemanquant, ou autowiringApp\:qui auto-configure et conflit. Vérifierdebug:container --tag=sylius_fixtures.fixturepuis l'exclusionsrc/Fixture/dansservices.yaml. Unrecognized option "deliveryConditions" under "sylius_fixtures.suites.default.fixtures.shipping_method.options.custom.geis": laFixturen'a pas étenduconfigureResourceNode. Le YAML est validé par schéma — tout champ non déclaré explose au boot.parent::__construct()qui lèveToo few arguments: sur Sylius < 2.0.8 les args constructeur sontprivate; il faut redéclarer le constructeur complet avec tous les arguments du parent. Depuis 2.0.8, ils sontprotected→ un simpleparent::__construct(...func_get_args())suffit.- Multiple definitions for the same service id
sylius.fixture.example_factory.shipping_method: autowiringApp\:non exclu sursrc/Fixture/Factory/. Le service custom est défini deux fois (manuellement + auto). AjouterFixtureàexclude:. - Channel custom créé sans currencies/locales : la fixture
currency/localen'a pas tourné avantchannelparce qu'on a overridepriorityà la main. Laisser les priorités vendor par défaut (currency/locale ont priorité plus haute → tournent avant). - Champ translatable persisté sur une seule locale : la factory n'a pas bouclé sur les locales (pas de
setCurrentLocale/setFallbackLocale). Résultat :SELECT * FROM ..._translation WHERE locale = 'fr_FR'est vide alors queen_USest OK. sylius:fixtures:loadqui ne purge pas : sans listenerorm_purger, la suite est cumulative. Re-load → rows dupliquées, contraintes uniques qui claquent. Soit ajouterorm_purger, soitdoctrine:database:drop --force && doctrine:database:create && doctrine:migrations:migrate -navant.- Custom store fixture sur Sylius < 1.12 : l'option
customn'existait pas — il fallait override la fixture entière. Vérifiercomposer.locksi tu hérites d'un projet legacy. - Modifier la fixture vendor directement : ne jamais patcher
vendor/sylius/sylius/src/Sylius/Bundle/CoreBundle/Fixture/— réécrasé au prochaincomposer update. Toujours étendre viaApp\Fixture\.
Argument optionnel
/sylius:fixtures default channel CZ_WEB — ajoute un channel CZ_WEB dans la suite default, sans toucher au code PHP.
/sylius:fixtures shipping_method deliveryConditions:string — étend ShippingMethodExampleFactory + ShippingMethodFixture pour exposer un champ deliveryConditions (préalable : champ ajouté via /sylius:model).
/sylius:fixtures demo — crée une suite custom demo avec purge ORM.
/sylius:fixtures sans argument — demande le type d'opération (modifier default, exposer un champ custom, créer une suite dédiée) et déroule.