| name | validation |
| description | Customise la validation d'un resource Sylius — `config/validator/<Model>.yaml` + groupe custom via `sylius.form.type.*.validation_groups`. Champ absent → `/sylius:model`. Déclenche sur "ajouter une contrainte Sylius", "valider un champ custom". |
| user_invocable | true |
| allowed-tools | ["Read","Write","Edit","Glob","Grep","Bash(ls:*)","Bash(find:*)","Bash(cat:*)","Bash(git:*)","Bash(symfony:*)","Bash(bin/console:*)","Bash(composer:*)","Bash(vendor/bin/*:*)","Bash(./vendor/bin/*:*)"] |
/validation — Customiser la validation Sylius
Tu aides à redéfinir les contraintes de validation d'un resource Sylius (longueur mini d'un champ, plage de montants, règles métier) sans patcher le vendor. Le pattern officiel : écrire un fichier config/validator/<Model>.yaml|xml qui override les contraintes dans un nouveau groupe de validation (ex. app_product), puis rebrancher ce groupe via sylius.form.type.*.validation_groups dans config/services.yaml. Pour ShippingMethodRule, PromotionRule, PromotionAction, PromotionCoupon, CatalogPromotionAction, CatalogPromotionScope, ZoneMember, la logique passe par ChannelCodeCollection + un paramètre indexé par la clé de règle.
Référence officielle : docs.sylius.com/the-customization-guide/customizing-validation.
Détection préalable (obligatoire)
- Lire
composer.json à la racine.
- Vérifier
sylius/sylius dans les dépendances.
- Présent → OK.
- Absent → « Ce skill cible Sylius (override validation via groupes
sylius.form.type.*.validation_groups). Je ne trouve pas sylius/sylius. On continue quand même ? »
- Si la demande concerne un champ qui n'existe pas encore sur le modèle (ex. valider un
secondaryPhoneNumber jamais ajouté) → basculer sur /sylius:model pour créer le champ + migration, puis revenir ici pour poser la contrainte.
- Si la demande est « ajouter une contrainte
#[Assert\*] directement en attribut PHP sur une entité custom » (pas une entité Sylius native) → basculer sur /symfony:validation-constraints : la stratégie n'est pas de créer un groupe Sylius parallèle, c'est de valider l'entité applicative directement.
Règles fondamentales
Déroulement
1 — Cadrer le besoin
Demander (ou confirmer si déjà fourni) :
- Resource cible :
Product, ProductTranslation, Customer, ShippingMethodRule, PromotionRule, PromotionCoupon, Address, Channel, etc.
- Champ + contrainte :
name → min 10 / max 255 ; amount → range 1000-1000000 ; code → NotBlank custom ; etc.
- Portée : contrainte sur un form classique (shop / admin), ou sur une
*Rule / *Action (clé dynamique de configuration).
- Nom du groupe custom : convention
app_<resource> (ex. app_product, app_customer, app_shipping_method_rule_order_grater_than_or_equal). Choisis un nom stable, il est cité dans plusieurs endroits.
- Locale(s) des messages : pour prévoir les clés de traduction.
2 — Repérer le fichier vendor de référence
Prendre la version exacte installée (ne pas taper au hasard sur master) :
composer show sylius/sylius | grep -i '^versions'
ls vendor/sylius/sylius/src/Sylius/Bundle/*/Resources/config/validation/ | grep -i <Model>
Ouvrir ce fichier : il liste toutes les contraintes et les groupes (sylius, parfois sylius_*). Sert de base de copie à adapter. Ne jamais le modifier en place dans vendor/.
3 — Cas standard : champ sur un resource classique
Exemple : forcer min: 10 sur Product.name (stocké dans ProductTranslation.name).
a. Créer config/validator/ProductTranslation.yaml
Sylius\Component\Product\Model\ProductTranslation:
properties:
name:
- NotBlank:
message: sylius.product.name.not_blank
groups: [app_product]
- Length:
min: 10
minMessage: sylius.product.name.min_length
max: 255
maxMessage: sylius.product.name.max_length
groups: [app_product]
Équivalent XML si la team utilise exclusivement XML :
<?xml version="1.0" encoding="UTF-8"?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Component\Product\Model\ProductTranslation">
<property name="name">
<constraint name="NotBlank">
<option name="message">sylius.product.name.not_blank</option>
<option name="groups">app_product</option>
</constraint>
<constraint name="Length">
<option name="min">10</option>
<option name="minMessage">sylius.product.name.min_length</option>
<option name="max">255</option>
<option name="maxMessage">sylius.product.name.max_length</option>
<option name="groups">app_product</option>
</constraint>
</property>
</class>
</constraint-mapping>
Points de vigilance :
- Toutes les contraintes de la property doivent pointer sur
groups: [app_product] — si une contrainte reste sur [sylius], elle ne sera plus appliquée une fois le form rebranché sur app_product.
- Si tu conserves certaines contraintes du vendor telles quelles, copie-les aussi dans ton YAML avec le nouveau groupe. Sinon elles disparaissent lors du passage au groupe custom.
b. Rebrancher le form sur le nouveau groupe
config/services.yaml :
parameters:
sylius.form.type.product_translation.validation_groups: [app_product]
sylius.form.type.product.validation_groups: [app_product]
Pour trouver le nom exact du paramètre à override :
symfony console debug:container --parameters --env=dev | grep validation_groups
La sortie liste tous les paramètres de la forme sylius.form.type.<resource>.validation_groups — copie-colle le bon nom, sans deviner (la convention n'est pas 100 % régulière : product_translation, pas product.translation).
c. Vérifier
symfony console cache:clear
symfony console debug:container --parameters | grep app_product
Soumettre un form admin avec un name de 5 caractères : l'erreur sylius.product.name.min_length doit apparaître.
4 — Cas spécial : ShippingMethodRule, PromotionRule, etc.
Exemple : imposer un montant minimum de 10 € (1000 centimes) pour la rule order_total_greater_than_or_equal.
a. Créer config/validator/ShippingMethodRule.yaml
Sylius\Component\Shipping\Model\ShippingMethodRule:
properties:
configuration:
- Sylius\Bundle\CoreBundle\Validator\Constraints\ChannelCodeCollection:
groups: app_shipping_method_rule_order_grater_than_or_equal
validateAgainstAllChannels: true
channelAwarePropertyPath: shippingMethod
constraints:
- Collection:
fields:
amount:
- range:
groups: app_shipping_method_rule_order_grater_than_or_equal
min: 1000
max: 1000000
allowExtraFields: true
Points de vigilance :
ChannelCodeCollection wraps obligatoirement le Collection dès qu'une règle est ChannelAware (c'est le cas de toutes les rules Sylius). Sans ce wrapping, la contrainte ne s'applique qu'au canal par défaut.
channelAwarePropertyPath: shippingMethod pointe vers la property parente qui expose les channels (ici, ShippingMethodRule::$shippingMethod). Pour une PromotionRule, c'est promotion. Pour CatalogPromotionAction, c'est catalogPromotion.
allowExtraFields: true est non-négociable : les payloads configuration comportent des clés variables selon le type de rule.
- Les valeurs de
min/max sont en centimes (toute somme monétaire dans Sylius est stockée en plus petite unité).
b. Rebrancher via un paramètre indexé par la clé de rule
config/services.yaml :
parameters:
sylius.shipping.shipping_method_rule.validation_groups:
order_total_greater_than_or_equal: [app_shipping_method_rule_order_grater_than_or_equal]
c. Vérifier depuis l'admin
Créer une ShippingMethod avec la rule order_total_greater_than_or_equal et un montant amount: 500 (5 €) → l'admin doit rejeter avec l'erreur range. Montant 1500 → pass.
5 — Messages de validation : traductions
Chaque clé utilisée dans message / minMessage / maxMessage doit exister côté translations :
sylius:
product:
name:
not_blank: Please enter product name.
min_length: Product name must be at least {{ limit }} characters long.
max_length: Product name cannot be longer than {{ limit }} characters.
Domaine : validators est le domaine natif Symfony pour les contraintes de validation. Si tu utilises le domaine messages (par défaut), déclarer-les dans messages.<locale>.yaml et utiliser messageTemplate + domain: messages dans la contrainte.
Éviter le piège classique : si la clé existait déjà côté Sylius vendor (comme sylius.product.name.min_length) mais que le paramètre de message vendor attend un placeholder {{ limit }} différent, le template ne remplacera pas la variable — préfixer les clés custom avec app. évite tout chevauchement.
6 — Group Sequence Validation (avancé)
Quand plusieurs contraintes doivent s'exécuter dans un ordre précis (valider code avant name, par exemple), Symfony supporte le group sequence provider. Pour que Sylius l'active, le form doit rester en validation group [Default] :
use Symfony\Component\Validator\GroupSequenceProviderInterface;
class Product extends BaseProduct implements GroupSequenceProviderInterface
{
public function getGroupSequence(): array
{
return ['Product', 'app_product_strict'];
}
}
Côté config/services.yaml, laisser :
parameters:
sylius.form.type.product.validation_groups: [Default]
Réf : Symfony — Group Sequence Provider. Pas évident à débugger : activer seulement si la team en a réellement besoin.
7 — Vérification finale
symfony console cache:clear
symfony console debug:container --parameters | grep -E '(app_product|app_shipping)'
Tester le parcours qui déclenche la validation :
- Form shop/admin : saisir une valeur invalide → message attendu s'affiche.
- Rule (shipping / promotion) : configurer un
amount hors range depuis l'admin → l'admin refuse la soumission.
- API :
POST / PUT sur la ressource concernée → réponse 422 avec la violation bien taggée.
Si la contrainte ne se déclenche pas :
- le form n'envoie pas le bon
validation_groups → vérifier sylius.form.type.*.validation_groups ;
- la clé de rule ne matche pas → vérifier la liste des
rule_checker / action_checker ;
- la contrainte tombe encore dans le groupe
sylius → un groups: [...] a été oublié dans le YAML ;
- la translation manque → on voit la clé
sylius.product.name.min_length brute au lieu du texte.
8 — Clôture
Afficher :
- Fichiers créés/modifiés :
config/validator/<Model>.yaml, config/services.yaml (paramètres validation_groups), translations/validators.<locale>.yaml.
- Commandes de vérif :
debug:container --parameters, debug:container --tag=sylius.*.rule_checker, cache:clear.
- Ce qui reste à faire : couvrir les autres rule keys si la contrainte doit être uniforme, ajouter les traductions pour toutes les locales actives, tests fonctionnels (form submit + API
422).
Pièges fréquents
- Override partiel : ne redéclarer que
Length dans le YAML en pensant garder NotBlank du vendor. Une fois le group basculé sur app_product, plus rien du vendor ne s'applique — NotBlank doit être recopié.
- Oublier
sylius.form.type.product.validation_groups quand le champ est sur ProductTranslation : le form parent continue d'émettre le groupe sylius, donc tes contraintes custom ne sont jamais validées.
- Rule key fausse :
order_total_greater_or_equal au lieu de order_total_greater_than_or_equal. Le paramètre est ignoré silencieusement, la contrainte retombe sur la default. Toujours lister via debug:container --tag=sylius.shipping_method.rule_checker.
- Oublier
allowExtraFields: true sur Collection dans configuration : le premier champ imprévu fait exploser la validation complète de la rule.
- Montants en euros au lieu de centimes dans
min/max d'une range : min: 10 laisse passer tout ≥ 10 centimes. Tous les montants Sylius sont en centimes → multiplier par 100.
- Modifier le fichier
vendor/sylius/…/validation/*.xml directement : saute au composer update suivant, casse les autres projets qui partagent le vendor. Toujours créer le fichier côté config/validator/.
#[Assert\*] en attribut PHP sur un override d'entité + fichier XML vendor qui a encore le même champ : le merge Symfony fusionne les contraintes, les erreurs se démultiplient (deux messages NotBlank au lieu d'un). Choisir un seul endroit (YAML/XML recommandé pour rester alignés avec la doc Sylius).
- Groupe
[Default] attendu par getGroupSequence() mais paramètre laissé à [app_product] : getGroupSequence() n'est jamais appelé (condition d'entrée non satisfaite). Lire la doc Symfony sur le sujet avant d'activer le pattern.
- Cache pas vidé : la modif du YAML ou du paramètre ne prend pas effet.
cache:clear systématique sur dev et prod après un déploiement.
Argument optionnel
/sylius:validation ProductTranslation name min=10 max=255 — override la longueur de Product.name via ProductTranslation, groupe auto app_product.
/sylius:validation ShippingMethodRule order_total_greater_than_or_equal amount min=1000 max=1000000 — contrainte range sur la rule order_total_greater_than_or_equal, groupe auto app_shipping_method_rule_order_grater_than_or_equal.
/sylius:validation PromotionCoupon code pattern=/^[A-Z0-9]{6,12}$/ — contrainte regex sur le code coupon via PromotionCoupon.
/sylius:validation sans argument — demande la resource cible, le champ, les contraintes, et guide pas à pas (standard ou cas spécial rule/action).