Markus Michalski FAQ

Sylius SEO Suite Pro: Technical Details

Technical details of Sylius SEO Suite Pro: SeoData pipeline diagram, eight tables with ER diagram, migrations, plugin structure, request lifecycle, naming conventions and testing.

Deutsche Version

Back to overview

Sylius SEO Suite Pro: Technical Details

This page describes the plugin's structure for integrators: the data flow of a request, the database tables, the directory layout and the naming conventions. Extension points are covered on the extending page.

Architecture: the SeoData pipeline

Storefront and Shop API read from the same source, the SeoDataProviderInterface. This keeps the two frontends from drifting apart. The result is an immutable SeoData object holding meta data, Open Graph, Twitter Card, structured data and hreflang alternates.

graph TD
    REQ["Storefront request"] --> HOOK["Twig hook sylius_shop.base#metatags"]
    HOOK --> FN["Twig function mmd_seo_suite_pro_seo"]
    FN --> CTX["RequestSubjectResolver: product, category or home page"]
    CTX --> PROV["SeoDataProviderInterface"]
    API["API Platform: GET /shop/seo/products/code and /shop/seo/taxons/code"] --> APIP["SeoResourceProvider"]
    APIP --> PROV
    PROV --> META["SubjectMetaDataResolver: meta title, description, canonical, robots"]
    META --> TPL["SEO template renderer and placeholders"]
    META --> FACET["FacetRobotsPolicy"]
    PROV --> SCHEMA["StructuredDataBuilder: schema providers and enrichers"]
    PROV --> SOCIAL["SocialTagsFactory and OgImageCardUrlResolver"]
    PROV --> HREF["HreflangAlternatesBuilder"]
    META --> DATA["SeoData"]
    SCHEMA --> DATA
    SOCIAL --> DATA
    HREF --> DATA
    DATA --> TWIG["Template shop/metatags.html.twig"]
    DATA --> RES["API resource Seo"]
    TWIG --> TITLE["TitleListener replaces title on kernel.response"]

The steps in detail:

Tables and data model

The plugin creates nine tables. All carry the prefix mmd_sylius_seo_suite_pro_, and none has a foreign key to a Sylius core table. The link to a channel is purely logical through the channel_code column.

erDiagram
    SYLIUS_CHANNEL ||--o| CHANNEL_SETTINGS : channel_code
    SYLIUS_CHANNEL ||--o{ SEO_TEMPLATE : channel_code
    SYLIUS_CHANNEL ||--o| HREFLANG_SETTINGS : channel_code
    SYLIUS_CHANNEL ||--o| SCHEMA_SETTINGS : channel_code
    SYLIUS_CHANNEL ||--o| OG_IMAGE_SETTINGS : channel_code
    SYLIUS_CHANNEL ||--o{ REDIRECT : channel_code
    SYLIUS_CHANNEL ||--o{ NOT_FOUND_LOG : channel_code
    SYLIUS_CHANNEL ||--o{ AUDIT_RUN : channel_code
    AUDIT_RUN ||--o{ AUDIT_ISSUE : run_id

The diagram shows logical relationships only. There are no database foreign keys, neither between the plugin and Sylius nor between an audit run and its findings. The link runs through channel_code and run_id. For a redirect, channel_code is optional (empty means all channels).

Entity Table Purpose
ChannelSettings mmd_sylius_seo_suite_pro_channel_settings Per-channel settings: robots.txt content, facet robots, social tags, Open Graph and Twitter details
SeoTemplate mmd_sylius_seo_suite_pro_template Title and description templates per channel, language (empty = all) and type
HreflangSettings mmd_sylius_seo_suite_pro_hreflang_settings hreflang per channel: activation, group, x-default language
SchemaSettings mmd_sylius_seo_suite_pro_schema_settings Attribute mapping for GTIN, MPN, brand and additional properties in the Product node
OgImageSettings mmd_sylius_seo_suite_pro_og_image_settings Design and switches of the generated social cards per channel
Redirect mmd_sylius_seo_suite_pro_redirect Redirects (manual, automatic, import) with a hit counter
NotFoundLog mmd_sylius_seo_suite_pro_not_found_log Log of 404 paths without a redirect
AuditRun mmd_sylius_seo_suite_pro_audit_run One audit run per channel with status, heartbeat and lock (internal, plain SQL)
AuditIssue mmd_sylius_seo_suite_pro_audit_issue A single finding of an audit run

The entities (and their interfaces) live in src/Entity. The resources are registered with Sylius under the alias prefix mmd_sylius_seo_suite_pro_plugin.

Migrations

There are eight migrations in src/Migrations, one per feature block (channel settings, social columns of the channel settings, templates, redirects with the 404 log, hreflang, schema, audit, OG images). They are written for MySQL 8 and MariaDB only: on any other platform every migration skips itself (skipIf) and is recorded as executed, so the tables are not created.

The path columns of Redirect (source, source_key, target) and NotFoundLog (path, path_key) use the collation utf8mb4_bin. Redirect sources are compared through the lower-cased source_key, so matching is case-insensitive while the original path is preserved exactly. The audit lock relies on the unique lock_key, which is set to NULL after a run.

The migrations are executed with bin/console mmd:seo-suite-pro:setup. The command runs only this plugin's open migrations, never the host's, and requires the Sylius core migrations to be done first. Doctrine's table doctrine_migration_versions is the single source of truth. See installation.

Plugin structure

sylius-seo-suite-pro/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Api/                 API Platform resource Seo and state provider
β”‚   β”œβ”€β”€ Audit/               audit run, store, dashboard and the checks (Check/)
β”‚   β”œβ”€β”€ Command/             console commands (setup, audit, og-image, redirects)
β”‚   β”œβ”€β”€ Controller/          /robots.txt, card route /seo/og/... and admin controllers (Admin/)
β”‚   β”œβ”€β”€ DependencyInjection/ configuration tree and bundle extension
β”‚   β”œβ”€β”€ Doctrine/            listener for automatic redirects on slug change
β”‚   β”œβ”€β”€ Entity/              Doctrine entities and their interfaces
β”‚   β”œβ”€β”€ EventListener/       404 redirect, title replacement, resource listener for redirects
β”‚   β”œβ”€β”€ Factory/             redirect prefilled from the 404 log
β”‚   β”œβ”€β”€ Form/                admin forms (Type/) and SEO fields on the translation forms (Extension/)
β”‚   β”œβ”€β”€ Grid/                custom grid filters
β”‚   β”œβ”€β”€ Hreflang/            hreflang providers, builder, URL building and settings lookup
β”‚   β”œβ”€β”€ Menu/                admin sidebar entry
β”‚   β”œβ”€β”€ Migrations/          the eight migrations
β”‚   β”œβ”€β”€ Model/               interfaces and traits for the shop's translation classes
β”‚   β”œβ”€β”€ OgImage/             planner, source loading, renderer (GD), file cache, prune
β”‚   β”œβ”€β”€ Redirect/            path rules, store, chains, 404 recorder, CSV import and export
β”‚   β”œβ”€β”€ Schema/              schema providers (JSON-LD), enrichers, GTIN check, attribute loader
β”‚   β”œβ”€β”€ Seo/                 meta data, SeoData, provider, template renderer, facets, sitemap exclusion
β”‚   β”œβ”€β”€ Settings/            repositories for the settings entities
β”‚   β”œβ”€β”€ Twig/                Twig functions and filters
β”‚   β”œβ”€β”€ Validator/           constraints for templates, redirects, hreflang and schema
β”‚   └── MmdSyliusSeoSuiteProPlugin.php   bundle class
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ config.yaml          base bundle configuration
β”‚   β”œβ”€β”€ services.php         service definitions and tags
β”‚   β”œβ”€β”€ grids/               admin grids
β”‚   β”œβ”€β”€ routes/              admin.yaml and shop.yaml (to be imported by the host)
β”‚   └── twig_hooks/          hook registrations for admin and shop
β”œβ”€β”€ Resources/fonts/         shipped fonts DejaVu Sans and Sans Bold for the OG cards
β”œβ”€β”€ templates/
β”‚   β”œβ”€β”€ admin/               admin templates (audit, redirects, forms, grid actions)
β”‚   └── shop/metatags.html.twig   head template for the storefront
β”œβ”€β”€ translations/            messages and validators in German and English
└── tests/                   unit, integration, functional, Behat and TestApplication

Lifecycle of a storefront request

On every product, category and home page the SeoData pipeline runs exactly once. The result is stored as an attribute on the main request (_mmd_seo_suite_pro_data), so further calls of the Twig function within the same request cost nothing. The title is inserted into the finished HTML at kernel.response.

Cost and memoisation per request:

Naming conventions

Area Prefix or pattern Example
Console commands mmd:seo-suite-pro: mmd:seo-suite-pro:audit:run
Database tables mmd_sylius_seo_suite_pro_ mmd_sylius_seo_suite_pro_redirect
Configuration root mmd_sylius_seo_suite_pro config/packages/mmd_sylius_seo_suite_pro.yaml
Service IDs, parameters, translation keys mmd_sylius_seo_suite_pro. mmd_sylius_seo_suite_pro.sitemap_exclusion
Service tags (extension points) mmd_sylius_seo_suite_pro.<kind> mmd_sylius_seo_suite_pro.schema_provider
Manual admin routes mmd_sylius_seo_suite_pro_admin_ mmd_sylius_seo_suite_pro_admin_audit
Generated resource routes mmd_sylius_seo_suite_pro_plugin_admin_<resource>_<action> mmd_sylius_seo_suite_pro_plugin_admin_seo_redirect_index
Shop routes mmd_sylius_seo_suite_pro_ mmd_sylius_seo_suite_pro_robots_txt, mmd_sylius_seo_suite_pro_og_image
Own hook roots mmd_sylius_seo_suite_pro. mmd_sylius_seo_suite_pro.seo_audit.index
Hook keys inside core hooks, Twig functions mmd_seo_suite_pro_ mmd_seo_suite_pro_seo
Resource alias and UI keys mmd_sylius_seo_suite_pro_plugin mmd_sylius_seo_suite_pro_plugin.ui.<resource>

Resource names carry a prefix (for example seo_suite_pro_channel_settings) because Sylius names the admin hooks after the resource part of the alias only. A generic name would show this plugin's buttons on other plugins' pages. Admin URLs and table names are not affected.

The admin routes sit under %sylius_admin.path_name%, not under a hardcoded /admin. The shop routes (/robots.txt and /seo/og/...) belong at the top level and not under the locale prefix.

Classes marked @internal (for example the admin controllers and some listeners) are not extension API. Anything not covered by the interfaces on the extending page counts as an implementation detail. Extend through the tagged interfaces, see extending.

Development and tests

Little of this matters for integrators. The repository ships a Docker environment, no local PHP is needed:

make init       # copy compose.override.yml, composer install, build assets, start containers
make static     # PHPStan and ECS
make phpunit    # unit, integration and functional tests
make php-shell  # shell in the PHP container

The integration and functional tests build the schema themselves from the ORM mapping and need only a reachable database (APP_ENV=test). tests/TestApplication holds the product and category subclasses with their migration, as a shop writes them to opt in to the SEO fields. It is the reference for the guide in the installation page.