Support Form: Technical Details
Support Form for Sylius - flow of a request as a diagram, dual mode at a glance, plugin structure, the six tables, migrations and details of the admin interface
Support Form: Technical Details
Live Demo: Try this and other plugins live β no installation, no risk: demo.markus-michalski.net
Flow of a request
Storefront and Shop API run into the same processing. There the event is dispatched first, and the request only continues if no listener rejects it.
graph TD
A["Customer: POST /support"] --> B{"Form valid?"}
H["Client: POST /shop/support-requests"] --> I["Entity validation"]
I --> J["Check subject against select list"]
J --> K["Validate answers with form rules"]
B -->|Yes| M["Derive answers and e-mail address"]
K --> M
M --> N["Resolve subject code into label"]
N --> O["PRE_PERSIST event"]
O --> Q{"Rejected by a listener?"}
Q -->|Yes| U["Storefront: error message, API: 422"]
Q -->|No| R["Store request"]
R --> S["Mail to the operator"]
R --> T["Confirmation to the customer"]
S --> V["Storefront: success message, API: 201"]
T --> V
The processing (SupportRequestSubmissionHandler) is the only place where requests are stored and notifications are sent. Controller and API processor only call it. Each of the two mails has its own error guard: if one fails, the request is still stored and the other one goes out. Only the exception class and code are logged, never the message, because it can contain personal data.
Dual mode at a glance
| Capability | Storefront | Shop API |
|---|---|---|
| Submit a request | GET/POST /support |
POST /shop/support-requests |
| Extra fields | In the form as field_<ID> |
GET /shop/support-form-fields |
| Answers | Input fields field_<ID> |
Object answers, keyed by the ID of the field |
| Subject choices | "Subject" field in the form | GET /shop/support-form-subject-choices |
| Confirmation to the customer | If the request has an address | If answers contains an answered e-mail |
| View requests | /admin/support-requests |
GET /admin/support-requests |
Plugin structure
src/
βββ Api/ # Shop API processor and provider, answer validation, subject choices as a DTO
βββ Command/ # SetupCommand, PurgeSupportRequestsCommand
βββ Controller/ # SupportRequestController (storefront)
βββ DependencyInjection/ # Bundle extension (empty configuration)
βββ Entity/ # SupportRequest, SupportFormField (+ translation), SupportFormChannelConfiguration, enums
βββ Event/ # SupportRequestEvents, SupportRequestPrePersistEvent
βββ Factory/ # SupportFormFieldFactory
βββ Form/Type/ # SupportRequestType and the admin forms
βββ Handler/ # SupportRequestSubmissionHandler
βββ Mailer/ # SupportRequestMailer, Emails
βββ Menu/ # Admin sidebar entry in the "MMKreativ Plugins" group
βββ Migrations/ # Three Doctrine migrations
βββ Repository/ # Repositories of the three resources
βββ Resolver/ # Subject choices, answer collector, privacy link
βββ Twig/ # PluginVersionExtension
config/
βββ grids/ # Three admin grids
βββ routes/ # admin.yaml, shop.yaml
βββ twig_hooks/ # admin.yaml, shop.yaml
βββ mailer.yaml # Two e-mail templates
templates/
βββ admin/ # Title blocks and forms, detail view
βββ emails/ # Notification and confirmation
βββ shop/ # Form and footer link
translations/ # messages, validators, flashes in de and en
Tables and migrations
The plugin creates six tables.
graph LR
A["support_request"] -->|"channel_id, SET NULL"| C["sylius_channel"]
B["support_form_channel_configuration"] -->|"channel_id, CASCADE"| C
B --- D["configuration_product"]
B --- E["configuration_taxon"]
F["support_form_field"] --- G["support_form_field_translation"]
| Table | Content |
|---|---|
mmd_sylius_support_form_support_request |
Incoming requests: created_at, email, subject, consent, channel_id, field_values |
mmd_sylius_support_form_field |
Extra fields: field_type, required, position, limits, select_options |
mmd_sylius_support_form_field_translation |
Label and placeholder per language |
mmd_sylius_support_form_channel_configuration |
One row per channel: subject_mode, recipient_email, privacy_policy_page |
mmd_sylius_support_form_channel_configuration_product |
Assigned products |
mmd_sylius_support_form_channel_configuration_taxon |
Assigned categories |
The answers to the extra fields are stored as a JSON list of label and value in the field_values column of the request, not in a table of their own. The plugin never reads them individually, only together with the request. Because each answer carries its label, it stays readable when you later change or delete the field.
The channel_id column of a request is set to NULL when a channel is deleted, whereas the channel's configuration is deleted with it.
| Migration | Change |
|---|---|
Version20260928095114 |
Creates all six tables |
Version20260929120000 |
Adds recipient_email to the channel configuration |
Version20260929140000 |
Adds privacy_policy_page to the channel configuration |
Admin details
- The plugin reads no bundle configuration, the configuration class is empty.
- The plugin attaches grids and Twig hooks to the application configuration itself, so no import of your own is needed.
- The form for channel configurations uses the Sylius autocomplete fields for products and categories. The plain select field wouldn't provide a working selection in this Sylius version.
- The admin title block shows the installed plugin version (Twig function
support_form_plugin_version(), readsComposer\InstalledVersions). For a tagged release this is the version number, for a local path installation it isdev-mainplus the first characters of the commit hash. - The menu entry lives in the "MMKreativ Plugins" group. Each plugin creates the group itself if it is missing. Several MMKreativ plugins therefore share one sidebar entry.