Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,16 @@ This does not scale, when we create new folders for the compiled theme files on
So the asset files are now stored in a separate folder, that is not dependent on the sales channel or the theme configuration.
We use the `themeId` as the folder name, so the assets are still unique per theme, but they are not duplicated for every sales channel.

### PaaS / Platform.sh
### PaaS / Upsun

Platform.sh currently does not offer to store the theme assets on an internal storage, therefore the assets need to be stored locally.
Additionally, Platform.sh uses `immutable deploys`, meaning that once a version is deployed the file system is read-only and no changes can be made to the local files.
Upsun currently does not offer to store the theme assets on an internal storage, therefore the assets need to be stored locally.
Additionally, Upsun uses `immutable deploys`, meaning that once a version is deployed the file system is read-only and no changes can be made to the local files.

The theme compile is executed on PaaS during the `build` step, where there is no DB connection, so we can't use the new default implementation of the `AbstractThemePathBuilder`, which stores the new `seed` in the DB during the theme compile.
But because of the `immutable deploys` it is not possible to recompile the theme at runtime, a new deployment is needed to recompile the theme.
So PaaS was not affected by the issues during the theme compilation, and instead of rollbacking to a backup theme folder you would rollback to the last deployment instead.

That means that PaaS does not need the seeding mechanism, so we add a implementation for the `AbstractThemePathBuilder` that ignores the seed and will always return the same path for a given theme and sales channel combination (like the old default implementation).

Once Platform.sh allows to store the theme assets externally we can move the theme compile from the `build` to the `deploy` step and can then use the default `seeding` implementation, as we have access to the DB in the deploy step.
Once Upsun allows to store the theme assets externally we can move the theme compile from the `build` to the `deploy` step and can then use the default `seeding` implementation, as we have access to the DB in the deploy step.
Then you can also recompile the theme at runtime and PaaS will also benefit from the new theme compile mechanism.
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,31 @@ You can find the original version [here](https://github.com/shopware/shopware/bl

## Context

The process of configuring dependencies has been upgraded with various new features in recent versions of Symfony.
Recent versions of Symfony have introduced various new features for dependency management that simplify service configuration and improve the developer experience.

We would like to utilise new features such as:
These features are now available in Shopware and include:

* [Autowiring](https://symfony.com/doc/current/service_container.html)
* [PHP configuration](https://symfony.com/doc/current/service_container/import.html)
* [Attributes for autowiring](https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes)

## Decision

1. Autowiring will be enabled
2. Support will be added to load service configuration from PHP files (as well as XML for backwards compatibility)
3. Where services need a particular non default service, for example a different implementation, or scalar values, we can use attributes.
Shopware now supports the following modern Symfony dependency management features:

Note: Attributes should only be used in framework glue code, for example, in Controllers and commands. We do not want to couple our domain code too close to Symfony.
1. **Autowiring**: Services can be automatically resolved by Symfony using type hints, reducing the need for explicit service definitions.
2. **PHP-based service configuration**: Service configuration can be loaded from PHP files in addition to XML and YAML.
3. **Autowire attribute**: Services requiring non-default implementations or scalar values can use the `Autowire` attribute for configuration.

With autowiring enabled, we can greatly reduce the amount of configuration in the XML files since most of the configuration is unnecessary. Most dependency graphs can be automatically resolved by Symfony using type hints.
There are no runtime performance implications because the container with its definitions is compiled.
**Note**: Attributes should only be used in framework glue code, such as Controllers and Commands.
Domain code should remain decoupled from Symfony-specific implementations.

Advantages:
With autowiring enabled, dependency graphs can be automatically resolved by Symfony using type hints, significantly reducing configuration overhead.

* Less code to maintain.
* Better autocompletion with PHP.
* More modern approach
## Benefits for plugin development

## Backwards Compatibility / Migration Strategy
Plugin developers can now leverage these features to:

To migrate our current XML dependency configurations, we can follow the below steps:

Step 1: Add support for loading service definitions from PHP files as well as XML files.

Step 2: Enable autowiring. Symfony should prefer the registered configuration before trying to autowire. In other words, Symfony will only autowire classes without configured definitions.

Step 3: Delete definitions which are not required because they can be autoloaded.

Step 4: Migrate any existing definitions to the PHP configurations or Attributes.
* Reduce boilerplate configuration code
* Benefit from better IDE autocompletion with PHP-based configuration
* Use a more modern, streamlined approach
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Cache layer for navigation loader
date: 2025-08-19
area: framework & discovery
tags: [performance, cache, categories]
---

# Cache layer for navigation loader

::: info
This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository.
You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/adr/2025-08-19-cache-layer-for-navigation-loader.md)
:::

## Context
We see in multiple performance analysis that the navigation loader can be a bottleneck for the performance of the storefront, especially with a huge number of categories in the first levels of the category tree.
The navigation loader is responsible for loading the categories and their children, which are then used to render the navigation in the storefront.
This process can be quite expensive, not just because of the query time on the DB, but also for hydrating the entities into PHP objects.
The other part with a huge performance impact is the rendering performance on twig side for the many nested categories, however that is not part of this ADR.

The navigation loader is used in the storefront for every header and every listing page (when they use the sidebar navigation CMS element).
However, the data that is loaded is always the same for the same sales channel, because we always show/load the category tree for the main navigation of the sales channel to the depth that is configured in the sales channel config.

Adding support for ESI for the header and footer addresses the same fundamental performance issue, however it does not solve the problem for the listing pages, where the sidebar navigation CMS element is used.
Additionally, for ESI to be effective you would need a reverse proxy that supports ESI, and you need to disable compression your responses from your webserver, which could increase your infrastructure costs. So ESI is not a viable solution for every use case.

## Decision
We will implement a cache layer for the navigation loader to improve the performance of the storefront.
To not only safe the query time, but also the hydration time, we will store the categories as PHP serialized objects. That should be fine, because when the structure of the PHP objects changes, that means there is a new platform version, in which case the cache needs to be cleared anyway.
Also in order to be most effective and not store too much data, we will only cache the category tree for the main navigation for every sales channel and up to the depths that is configured in the sales channel, because that info is loaded on every header and every listing page (when they use the sidebar navigation CMS element).
We use the `CacheCompressor` to compress the serialized data before storing it in the cache, which should reduce the size of the cache entries significantly, however it adds some more processing time when reading and writing the cache entries.

This cache layer will work complementary to ESI, because ESI would also cache the rendered HTML of the navigation, the performance impact of ESI will be faster, but category information for the sidebar navigation is still loaded on every listing page, so this change is still beneficial in regard to performance, even when you use ESI.

## Consequences
* The loaded categories for the main navigation will be cached to the level defined in the sales channel config.
* Additional categories that might be loaded because e.g. the currently active category is below the configured depth will be loaded dynamically per request and merged with the default categories.
* The cache needs to be invalidated whenever a category is written or deleted. We use immediate cache invalidation for that, so that the behaviour is as before, even with deactivated HTTP-Cache.
* We encode the information about the `salesChannelId`, `language`, `root category id` and `depth` in the cache key, so that we can cache the categories for different sales channels and languages.
* We will add a `CategoryLevelLoaderCacheKeyEvent` so that plugins can modify the cache key if they dynamically influence which categories should be loaded/shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: Adding a country-agnostic language layer
date: 2025-09-01
area: discovery
tags: [administration, storefront, plugin, app, languages, language-pack, translations, crowdin]
---

# Adding a country-agnostic language layer

::: info
This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository.
You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/adr/2025-09-01-adding-a-country-agnostic-language-layer.md)
:::

## Context
Currently, Shopware’s language system uses a specific language locale (e.g. `de-DE` for German in Germany) for its translation, while falling back to another specific locale (e.g. `en-GB` for English in Great Britain).

This works well, but it is quite maintenance-heavy. If you use multiple versions or dialects of a language, such as British and American English, it will result in a lot of duplicated snippets.

### Some facts and figures
- Shopware has approximately 12,000 snippets.
- By default, the US translation is an automated copy of the GB translation, which is later adjusted by our community via Crowdin.
- When running them through a comparison tool, we find about 70 differences. This means that roughly 99.5% of our American snippets are identical to the British ones.

### Current structure

```mermaid
flowchart LR
subgraph "Specific language"
deDE("<strong>de-DE</strong><br/><em>German in Germany</em>")
deAT("<strong>de-AT</strong><br/><em>German in Austria</em>")
esES("<strong>es-ES</strong><br/><em>Spanish in Spain</em>")
esAR("<strong>es-AR</strong><br/><em>Spanish in Argentina</em>")
enUS("<strong>en-US</strong><br/><em>English in the USA</em>")
end
subgraph "System default language"
enGB("<strong>en-GB</strong><br/><em>English in Great Britain</em>")
end

deDE-->enGB
deAT-->enGB

esES-->enGB
esAR-->enGB

enUS-->enGB
```

## Decision
Another layer containing a country-agnostic language (e.g., `en` for both `en-GB` and `en-US`) will provide an additional fallback layer. This means dialects are now treated as patch files. In Shopware, `en-GB` will be renamed to `en`, as you have to select a base dialect, retaining its 12,000 snippets, while `en-US` will shrink to around 70 snippets. This also eliminates the need for tools to initially copy-paste or pre-translate from "English to English," which, of course, incurs costs.

## Consequences
- Every additional dialect’s snippet file (e.g., `en-US` for `en`) will shrink from around 12,000 entries to likely fewer than 100.
- We will also provide an additional layer to maximize compatibility with existing installations. This means that `en-GB` will still be available as a fallback for other languages like `de-DE`, but `en` will be the primary fallback for everything.
- We will also provide an additional layer to ensure maximum compatibility with existing translations (e.g. via extensions). This means `en-GB` will remain available as a fallback for other languages such as `de-DE` and `de`, while `en` will serve as the final fallback.
- Translations provided via the community (Crowdin) will show a lower coverage percentage, but will more accurately reflect the actual differences between `en-US` and `en`.
- In Crowdin and in our product, the base language is set declaratively, so we will inform the community that British English is our base language, and we simply refer to it as `en`.
- All snippet files in Shopware’s core will be renamed to the country-agnostic language
- `en-GB.json` -> `en.json`, `messages.en-GB.base.json` -> `messages.en.base.json` and so on.
- Similarly: `de-DE` -> `de`
- The community translations / language pack will update their locales to their country-agnostic versions as well (e.g., `es-ES` -> `es`).
- The system’s default language or active language handling will remain unchanged.
- Plugins and apps do not require immediate adjustments, as the previous structure will continue to function as before as well.

### New structure

```mermaid
flowchart LR
subgraph "Specific language (German)"
deDE("<strong>de-DE</strong><br/><em>German in Germany</em>")
deAT("<strong>de-AT</strong><br/><em>German in Austria</em>")
de("<strong>de</strong><br/><em>German</em>")
end
subgraph "Specific language (Spanish)"
esES("<strong>es-ES</strong><br/><em>Spanish in Spain</em>")
esAR("<strong>es-AR</strong><br/><em>Spanish in Argentina</em>")
es("<strong>es</strong><br/><em>Spanish</em>")
end
subgraph "Specific language (English)"
enUS("<strong>en-US</strong><br/><em>English in the USA</em>")
end
subgraph "Fallback (English)"
enGB("<strong>en-GB</strong><br/><em>English in Great Britain</em>")
en("<strong>en</strong><br/><em>English</em>")
end

deDE-->de
deAT-->de
de-->enGB

esES-->es
esAR-->es
es-->enGB

enUS-->en
enGB-->en

style de color:#6f6,stroke:#6f6
style es color:#6f6,stroke:#6f6
style en color:#6f6,stroke:#6f6

style enGB stroke-dasharray: 5
```