Matomo Analytics: Technical Details
Matomo Analytics for Sylius - flow of the tracking configuration as a diagram, fault tolerance when the table is missing, plugin structure and the database table with all columns
Matomo Analytics: Technical Details
This documentation always describes the latest published version.
Flow
All consumers, that is the storefront hooks and the Shop API, ask the same place: the resolver for the tracking configuration. It reads the settings of the channel and checks them with a single validity check. An incomplete entry therefore behaves like a disabled one everywhere, and no consumer derives its own idea of "configured enough".
graph TD
A["Twig hook or Shop API request"] --> B["Tracking configuration resolver"]
B --> C["Channel settings from the database"]
C --> D{"Row present?"}
D -->|"No"| N["null: render nothing / 404"]
D -->|"Yes"| E{"Main switch on, HTTPS URL, site ID above 0?"}
E -->|"No"| N
E -->|"Yes"| F{"Klaro gating on?"}
F -->|"No"| G["Render script directly"]
F -->|"Yes"| H["Script blocked as text/plain + consent bridge"]
H --> I["Klaro unlocks after consent"]
The resolver remembers the result per channel for the duration of the request, even if the hooks ask several times. There is therefore one database query per channel and request. The cache is cleared when the kernel is reset (important for long-running workers).
If the database access fails, for example because the table is still missing, the resolver returns null and writes a warning to the log channel mmd_matomo. This result is not cached: once the database is back, tracking resumes.
Output safety
- The Matomo URL is validated before output (HTTPS, no characters that could break out of the inline script). The JavaScript escaping at render time is a second safeguard, not the first.
- Names, SKUs, prices and quantities go through
json_encodewithJSON_HEX_TAG, so a product name cannot smuggle in a</script>.
Plugin structure
src/
βββ Api/ Shop API resource and provider
βββ Command/ Setup command
βββ DependencyInjection/ Extension (mapping, hooks, grid, migrations), empty Configuration
βββ Entity/ ChannelSettings and interface
βββ EventListener/ Create the Klaro service on save
βββ Form/Type/ Form of the channel settings
βββ Klaro/ Availability, catalog interface, provisioning
βββ Menu/ Sidebar entry
βββ Migrations/ Create the table
βββ Settings/ Repository
βββ Tracking/ Configuration, resolver, code renderer, order data
βββ Twig/ Twig functions
config/
βββ grids/ Settings grid
βββ routes/ Admin routes
βββ twig_hooks/ Hooks for admin, storefront and e-commerce
βββ services.php Service definitions
templates/ Tracking script, e-commerce snippets, admin title
translations/ German and English
Database
The plugin creates exactly one table: mmd_sylius_matomo_channel_settings, with one row per channel. The assignment runs through the channel code (text), not through a foreign key. A unique index on channel_code enforces one row per channel.
All columns are NOT NULL. The defaults in the table are set by the entity when a row is created, the database itself has no DEFAULT values. If you write rows into the table bypassing the plugin, you have to fill all columns yourself.
The admin routes are named mmd_sylius_matomo_integration_plugin_admin_channel_settings_index, _create, _update and _delete, the path is /<admin-path>/matomo-channel-settings (the admin path comes from the parameter sylius_admin.path_name).
| Column | Type | Entity default |
|---|---|---|
id |
integer, primary key | |
channel_code |
varchar(255), unique | |
enabled |
boolean | false |
matomo_url |
varchar(255) | empty |
site_id |
integer | 0 |
cookieless_tracking |
boolean | true |
respect_do_not_track |
boolean | true |
use_klaro_consent |
boolean | false |
klaro_service_name |
varchar(255) | matomo |
ecommerce_enabled |
boolean | true |
track_product_views |
boolean | true |
track_orders |
boolean | true |
heartbeat_enabled |
boolean | true |
heartbeat_interval |
integer | 15 |
track_outbound_links |
boolean | true |
Entity validation is deliberately loose: an incomplete entry can be saved, and whether it actually tracks is decided solely by the validity check at runtime.
The migration lives in the namespace Mmd\SyliusMatomoIntegrationPlugin\Migrations and not in the default namespace DoctrineMigrations, so that it does not collide with your project's migrations. It requires the Sylius core migrations to have run.