Markus Michalski FAQ

Product Custom Inputs: Technical

Technical details of Product Custom Inputs: flow diagram, entities and tables, plugin structure, migrations, separate validation for storefront and Shop API, tests.

Deutsche Version

Back to overview

Product Custom Inputs: Technical

This documentation always describes the latest published version.

Flow

graph TD
    A["Add-to-cart form"] --> B["Collect values on submit"]
    B --> C["sylius.cart_item_add, priority -100"]
    C --> D{"New line or merge?"}
    D -->|New line| E["Save values"]
    D -->|Merge| F["Overwrite existing values"]
    G["Shop API POST"] --> H{"Plugin enabled, cart, field matches?"}
    H -->|No| I["404 or 422"]
    H -->|Yes| E
    E --> J["Display via Twig function"]
    F --> J

The listener runs at priority -100 and thus after the Sylius listener. Only then is it known whether the line is new or was merged into an existing one.

Entities

erDiagram
    FIELD ||--o{ FIELD_TRANSLATION : has
    FIELD }o--o{ PRODUCT : attached_to
    FIELD ||--o{ ORDER_ITEM_VALUE : filled_by
    ORDER_ITEM ||--o{ ORDER_ITEM_VALUE : holds
Table Content
mmd_sylius_product_custom_inputs_field Field definition: type, required, position, limits, select options (JSON)
mmd_sylius_product_custom_inputs_field_translation Label and placeholder per locale, unique per field and locale
mmd_sylius_product_custom_inputs_field_product Field to product attachment (many-to-many)
mmd_sylius_product_custom_inputs_order_item_value Customer input, unique per order item and field
mmd_sylius_product_custom_inputs_settings Singleton row (ID 1) holding the "Enabled" switch

The attachment follows the ProductOption to Product pattern and sits on the field's side. The foreign key of the values lives on the value table, because OrderItem is a mapped superclass in Sylius that a plugin cannot extend with a relation.

Structure

src/
  Api/            Provider, processor, ShopOrderItemResolver
  Command/        SetupCommand
  Controller/     Settings page
  DependencyInjection/
  Entity/         Fields, translation, values, settings, field type enum
  EventListener/  CartItemAddCustomInputsListener
  Factory/        Factory for fields
  Form/           Admin forms, extensions for CartItemType and the product form
  Repository/     Repositories for fields, values and settings
  Menu/           AdminMenuListener, MenuGroup
  Migrations/     Three Doctrine migrations
  Twig/           OrderItemCustomInputValuesExtension
  Validation/     ProductCustomInputFieldStringValueValidator
config/
  grids/ routes/ twig_hooks/ services.php
templates/        admin/ and shop/

Migrations

Three migrations in the namespace Mmd\SyliusProductCustomInputsPlugin\Migrations, running after the Sylius core migrations: tables for field, translation and value, then the settings table with its initial row, finally the switch to globally attachable fields. They are executed through the setup command, which considers only these migrations and first checks that the Sylius core ones have run.

Validation

The storefront validates with typed Symfony form constraints, the Shop API with ProductCustomInputFieldStringValueValidator on the text format. These are two separate paths on purpose, because the input arrives in a different shape in each. The validator checks dates and times strictly, so values like 25:00 or 2026-02-30 are rejected.

Tests

PHPUnit: unit tests and functional tests (admin CRUD, API, form extensions, hook registration). The Behat scaffold exists but contains no scenarios yet. Static analysis with PHPStan at level max, coding standard with ECS.