Product Snippets: Technical Details
Product Snippets for Shopware - replacement flow, caching, data model and plugin structure, plus the verified differences between the plugin lines for Shopware 6.7 and for 6.5/6.6
Product Snippets: Technical Details
Live Demo: Try this and other plugins live β no installation, no risk: demo.markus-michalski.net
Replacement flow
graph TD
A["Twig filter mmd_snippets"] --> B["SnippetReplacer"]
B --> C["SnippetParser: regex finds placeholders"]
C --> D["SnippetRegistry: one lookup per identifier"]
D --> E{"In cache?"}
E -->|"Yes"| G["Content from cache"]
E -->|"No"| F["Repository search: identifier and active"]
F --> G
G --> H["HtmlSanitizer cleans the content"]
H --> I["Placeholder is replaced"]
| Step | Class | Behavior |
|---|---|---|
| Filter | Mmd\ProductSnippets\Twig\SnippetExtension |
Filter mmd_snippets and function mmd_snippet. Takes the context from the current request. Without a request the text is returned unchanged. |
| Replace | Mmd\ProductSnippets\Core\Content\Snippet\SnippetReplacer |
Collects the identifiers, loads the snippets, replaces via regex and sanitizes each content with the HtmlSanitizer. Placeholders without a hit (unknown, inactive or without content in the current language) stay in place. |
| Parse | Mmd\ProductSnippets\Core\Content\Snippet\SnippetParser |
Regex {{ snippet:<identifier> }} or {{ textbaustein:<identifier> }}, identifier made of a-z, A-Z, 0-9, - and _, case-insensitive. |
| Load | Mmd\ProductSnippets\Core\Content\Snippet\SnippetRegistry |
Cache layer in front of the mmd_product_snippet repository. |
Caching
- For each distinct identifier of a description, the registry queries the cache once. On a cache miss a repository search follows for that identifier. There is no combined query for several identifiers.
- The
cache.objectpool is used. The key contains identifier and language ID, so every language has its own entry. - The lifetime is 3600 seconds. A snippet that was not found is cached for that long as well.
- The plugin tags every entry with cache tags (identifier and all snippets) and invalidates through these tags if the cache adapter supports tags. Without tag support it only deletes the key without language ID, which does not hit the per-language entries, and invalidating all entries is skipped. The entries then only expire after their lifetime.
Invalidation
A subscriber (Mmd\ProductSnippets\Subscriber\CacheInvalidationSubscriber) listens to the entity events mmd_product_snippet.written and mmd_product_snippet.deleted:
- Written: The identifier's cache entry is invalidated only if the write operation contains the identifier, that is when creating.
- Deleted: All snippet entries are invalidated.
- Content or name changed: These fields live in the translation table. A save that only changes them contains no identifier and triggers no invalidation.
- Active toggled: Here the write operation contains only the
activefield, so there is no invalidation either. A deactivated snippet therefore stays visible, and a newly activated one stays in the cache as "not found".
In the last two cases the change appears after the 3600 seconds have expired at the latest, or immediately after bin/console cache:clear. The plugin also does not invalidate the HTTP cache of product pages.
Data model
| Table | Contents |
|---|---|
mmd_product_snippet |
id, identifier (VARCHAR 255, UNIQUE, collation utf8mb4_unicode_ci), active (default 1), timestamps |
mmd_product_snippet_translation |
mmd_product_snippet_id, language_id, name, content (LONGTEXT), timestamps. Deleting the snippet cascades to its translations. |
The entity is named mmd_product_snippet (fields identifier, active, name, content). All fields are exposed to the Admin API, so snippets can also be read and written through it. There is no identifier validation there either.
Plugin structure
src/
βββ MmdProductSnippets.php # Plugin class, drops the tables on uninstall
βββ Content/Snippet/ # Entity definitions (snippet and translation)
βββ Core/Content/Snippet/ # Parser, replacer, registry
βββ Migration/ # Creates the two tables
βββ Subscriber/ # CacheInvalidationSubscriber
βββ Twig/ # SnippetExtension (filter, function)
βββ Resources/
βββ app/administration/ # Admin module (list and detail page)
βββ config/services.xml
βββ views/storefront/ # Three template overrides, two are effective under Shopware 6.7
Of the three template overrides, storefront/page/product-detail/description.html.twig is inactive under Shopware 6.7 because the core no longer provides this template. Details are on the Storefront Integration page.
Differences of the plugin line for Shopware 6.5 and 6.6
The line mmd/sw66-product-snippets (Shopware ~6.5.0 || ~6.6.0) has the same feature set and essentially the same source code. Comparing both lines, mainly these points differ:
| Area | Line 6.7 | Line 6.5 / 6.6 |
|---|---|---|
| Composer package and Shopware requirement | mmd/sw67-product-snippets, ~6.7.0 |
mmd/sw66-product-snippets, ~6.5.0 \|\| ~6.6.0 |
Reading the content (SnippetRegistry) |
Direct access to the entity's content field (current language only) |
getTranslation('content') (reads the translated array, which includes the language fallback chain) |
| Admin pages (list and detail) | Page title via head(), mixins via Shopware.Mixin.getByName() |
Page title via metaInfo(), mixins from the destructured Mixin |
| Shipped admin bundle | Vite build under Resources/public/administration/assets |
Files under Resources/public/administration/js and .../css |
| Entity-layer classes | final, properties identifier and mmdProductSnippetId with a typed default value '' |
not final, properties without a default value |
Comment in the template box-standard.html.twig |
Note about \|raw |
without this note (comment only, no behavior) |
The difference in reading the content affects behavior: if the content is missing in the current language, the 6.5/6.6 line may fall back to the default language, while in the 6.7 line the placeholder stays unreplaced. That
getTranslation()takes the fallback chain into account is Shopware core behavior and cannot be proven from the plugin's source code.
Service definitions (services.xml), migration, tables, placeholder syntax, Twig filter and function, the template files and the admin texts match in both lines.