CDN Product Images: Technical
Architecture, decision logic and caching of the Sylius plugin CDN Product Images.
CDN Product Images: Technical
This documentation always describes the latest published version.
Live Demo: Try this and other plugins live β no installation, no risk: demo.markus-michalski.net
Decision logic
graph TD
A["Image path"] --> B{"Starts with http(s)?"}
B -->|No| C["local upload, unchanged"]
B -->|Yes| D{"Host matches CDN domain?"}
D -->|No| E["foreign URL, passthrough"]
D -->|Yes| F["extract s3 bucket/key"]
F --> G{"Signed URL cached?"}
G -->|Yes| H["return cached URL"]
G -->|No| I["call signing service"]
I --> J{"HTTP 200?"}
J -->|Yes| K["cache 24h, signed URL"]
J -->|No| L["cache 30s, original URL"]
Detecting "CDN image vs. local upload" is purely based on the path prefix (http(s)://) - Sylius' own UploadedImagePathGenerator only ever produces relative, hash-based paths that can never start that way. There is no new database column or migration for this.
Plugin structure
src/
βββ Cdn/ # Signing core: config, URL signer, preset map
βββ Command/ # mmd:cdn-product-images:setup
βββ Controller/Admin/ # Settings page + token removal
βββ DependencyInjection/ # Bundle extension, compiler pass, (empty) Configuration
βββ Entity/ # SigningCredentials (singleton settings row)
βββ Form/Extension/ # cdnUrl field on Product-/TaxonImageType
βββ Form/Type/ # SigningCredentialsType (admin form)
βββ Imagine/ # decorated imagine_filter Twig extension
βββ Menu/ # Admin sidebar entry
βββ Migrations/ # Doctrine migration for the settings table
βββ Repository/ # SigningCredentialsRepository
βββ Security/ # SigningTokenCipher (libsodium)
βββ Serializer/ # decorated Shop API image normalizer
βββ Uploader/ # CDN-aware image uploader + path detection
Decorated services
| Decorated service | Decorator class | Purpose |
|---|---|---|
liip_imagine.templating.filter_extension |
CdnAwareImagineFilterExtension |
makes imagine_filter CDN-aware on the Twig storefront/Admin |
sylius.uploader.image |
CdnAwareImageUploader |
prevents a CDN path from being deleted as if it were a local file |
sylius_api.normalizer.image |
CdnAwareApiImageNormalizer |
the same logic for the Shop API channel - only registered when SyliusApiBundle is present (its own compiler pass, load-order independent) |
Entity & migration
| Entity | Table | Purpose |
|---|---|---|
SigningCredentials |
mmd_cdn_signing_credentials |
singleton row (id always 1) holding CDN domain, signing API URL, encrypted token ciphertext, scheme override |
Not a Sylius Resource (no grid, no list, no create/delete) - the admin only ever edits this one row through the settings page.
Caching
| What | TTL | Why |
|---|---|---|
| Successfully signed URL | 24 hours | signatures are deterministic - avoids a network round-trip per image render |
| Failed signing attempt | 30 seconds | bounds a retry storm against the signing API's rate limit during an outage |
| Resolved credentials (entity β effective config) | 60 seconds | avoids a DB query per image render; an admin change takes effect within about a minute |
Resize mapping
Every Sylius Imagine filter size is mapped 1:1 to an imgproxy resize option (including format: webp and quality: 80, so a CDN image matches the quality of a locally processed one). An unknown filter name always falls back to passthrough - a wrongly guessed resize would be worse than none at all.