<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: shopware</title>
    <description>The latest articles on DEV Community by shopware (shopware).</description>
    <link>https://dev.to/shopware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7453%2Fb193c428-b5cd-41aa-a1a0-c086aa01645a.jpg</url>
      <title>DEV Community: shopware</title>
      <link>https://dev.to/shopware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shopware"/>
    <language>en</language>
    <item>
      <title>Preparing for Shopware 6.8: early guidance for developers</title>
      <dc:creator>Jonas Elfering</dc:creator>
      <pubDate>Thu, 29 Oct 2026 10:01:00 +0000</pubDate>
      <link>https://dev.to/shopware/preparing-for-shopware-68-early-guidance-for-developers-2c7m</link>
      <guid>https://dev.to/shopware/preparing-for-shopware-68-early-guidance-for-developers-2c7m</guid>
      <description>&lt;p&gt;At our October Hacktoberfest in Schöppingen, we came together with the community to share what we’ve been working on and explore what comes next.&lt;br&gt;
Preparing for Shopware 6.8 was part of that conversation: I walked through the latest developments, with a practical focus on what you can start doing today.&lt;br&gt;
From testing upcoming changes behind feature flags to checking compatibility and sharing feedback, there are steps that can make the next upgrade easier.&lt;br&gt;
In this blog post, we’ll walk through those learnings and show how to make preparation part of regular development work, rather than leaving it until release day.&lt;/p&gt;

&lt;p&gt;Our goal is to make 6.8 the smoothest, most predictable and easiest-to-upgrade Shopware 6 major release so far.&lt;br&gt;
Compatibility tooling, testable feature flags and community feedback give agencies and plugin developers more time to understand the changes and prepare their projects.&lt;/p&gt;

&lt;p&gt;You can already join the conversation in Discord &lt;strong&gt;&lt;a href="https://discord.com/channels/1308047705309708348/1557367351471644732" rel="noopener noreferrer"&gt;#feedback-6_8&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
Trying the available tools and sharing difficult cases now gives our team valuable input while the implementation and upgrade approach can still evolve.&lt;/p&gt;

&lt;p&gt;Shopware 6.8 is planned for 2027, with timing and scope subject to change as planning is still at an early stage.&lt;br&gt;
That gives you a useful preparation window: start identifying the work now and include it in next year’s plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a clear picture of the project
&lt;/h2&gt;

&lt;p&gt;The first step is to map each installation: its Shopware version, hosting requirements, plugins, custom code and integrations.&lt;br&gt;
That picture helps identify which changes could affect customer journeys or business processes, and provides the basis for a sequence of tasks and an initial estimate.&lt;/p&gt;

&lt;p&gt;The budget needs to cover both preparation and the major upgrade itself.&lt;br&gt;
Compatibility work, testing and coordination with plugin vendors take time even when the final version change becomes smaller.&lt;br&gt;
Making that work visible early helps merchants plan costs and schedule changes around their business.&lt;/p&gt;

&lt;p&gt;Tooling can help reveal dependencies and missing work.&lt;br&gt;
The project plan still needs someone who understands how the shop operates.&lt;/p&gt;

&lt;p&gt;Use that to decide where to start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the customer journeys and integrations that could behave differently.&lt;/li&gt;
&lt;li&gt;Use the major-version flag and compatibility checks to find affected code.&lt;/li&gt;
&lt;li&gt;Identify preparation work you can release separately, before the major upgrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test the behavior customers depend on
&lt;/h2&gt;

&lt;p&gt;Code can continue to run while an integration behaves differently.&lt;br&gt;
Some problems only become visible later, when a customer uses a particular discount or an order reaches another system.&lt;br&gt;
Feature flags offer an early way to explore these changes in a test environment, alongside automated tests and checks of real customer journeys.&lt;br&gt;
The following examples show where changed behavior could affect a shop and what teams can check in advance.&lt;/p&gt;

&lt;p&gt;For these checks, a successful request is only the starting point.&lt;br&gt;
A small change in the tax calculator is a good example: the code can still run, while the amounts your payment integration receives have changed.&lt;br&gt;
Build assertions around the result the customer or connected system depends on, rather than only checking that checkout returns successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching and personalized content
&lt;/h3&gt;

&lt;p&gt;The cache rework keeps HTTP caching active for cacheable pages even when customers are logged in or have items in their carts, allowing more efficient responses.&lt;br&gt;
With &lt;code&gt;CACHE_REWORK&lt;/code&gt; enabled in a test environment, compare personalized content across different customers and cart states.&lt;br&gt;
Check cache invalidation separately.&lt;br&gt;
Customizations that assume these responses are never cached could accidentally place customer-specific information in a shared response.&lt;br&gt;
Testing those assumptions protects both privacy and correctness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taxes and discounts
&lt;/h3&gt;

&lt;p&gt;We’re correcting rounding edge cases where the cart calculation could produce slightly incorrect amounts.&lt;br&gt;
The proportional cart tax changes reuse existing item taxes for percentage discounts, surcharges and split quantities.&lt;br&gt;
Even a one-cent correction can matter when orders are passed to payment, ERP or accounting systems, so we’re introducing the change behind a feature flag before making it part of the major upgrade.&lt;/p&gt;

&lt;p&gt;Use the same cart before and after enabling the flag, with a percentage discount and products at different tax rates.&lt;br&gt;
Compare the line-item taxes and final totals, then follow those values into the payment request and your downstream integrations.&lt;br&gt;
Include split quantities if your shop uses them.&lt;br&gt;
A difference of a cent is exactly the sort of result that can slip past a test which only checks whether an order was created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document generation
&lt;/h3&gt;

&lt;p&gt;The experimental &lt;code&gt;DOCUMENT_GENERATION_REWORK&lt;/code&gt; separates document data preparation from rendering, making different document types and output formats easier to extend.&lt;br&gt;
Existing Twig templates remain, while PHP integrations need review against the new extension points.&lt;br&gt;
Test custom data and generated output.&lt;br&gt;
The planned transition retains the old implementation as a fallback in 6.8, with removal planned for 6.9.&lt;/p&gt;

&lt;p&gt;Adding more formats has made the old system harder to extend, so testing only the PDF would leave part of that change unchecked.&lt;br&gt;
For example, take an invoice with custom data added by your plugin and generate it in every format you support, such as PDF and accessible HTML.&lt;br&gt;
Check that the data reaches each output and that your Twig customizations still render as intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flow execution
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FLOW_EXECUTION_AFTER_BUSINESS_PROCESS&lt;/code&gt; is already available for testing and is planned to become the default in 6.8.&lt;br&gt;
For HTTP requests, it allows the customer response to be sent before flow actions finish, reducing the impact of expensive or failing flows on checkout completion.&lt;br&gt;
Execution remains in the same process.&lt;br&gt;
Review assumptions about tags, state changes and other flow results being available immediately; transaction-critical work belongs in synchronous subscribers.&lt;/p&gt;

&lt;p&gt;Consider a flow that adds a tag to an order or customer.&lt;br&gt;
If your checkout response reads that tag to decide what to display, it can no longer assume the tag is already there.&lt;br&gt;
Test the response itself as well as the eventual database state: the flow may complete correctly while the page has already been rendered without its result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the growing compatibility toolkit
&lt;/h2&gt;

&lt;p&gt;The major-version flag, &lt;code&gt;V6_8_0_0=1&lt;/code&gt;, can expose upcoming incompatibilities where the corresponding checks are implemented.&lt;br&gt;
A flagged CI run alongside the normal run helps reveal what needs attention while retaining coverage of current behavior.&lt;br&gt;
Coverage is expanding, so interpret the results alongside behavioral tests and the official 6.8 upgrade guidance, which is still a work in progress.&lt;/p&gt;

&lt;p&gt;We’re also making compatibility information more precise.&lt;br&gt;
Released PHP attributes describe planned API changes more clearly, while the accompanying future-compatibility PHPStan rules remain experimental and opt-in.&lt;br&gt;
An open polyfill proposal explores easier support for several Shopware versions in one plugin release.&lt;/p&gt;

&lt;p&gt;Administration extensions already have experimental ESLint and type-checking support.&lt;br&gt;
Runtime guards are still being developed.&lt;br&gt;
Storefront template guards and warning prototypes are also progressing, with questions around multi-version support still being worked through.&lt;/p&gt;

&lt;p&gt;The Administration code already provides examples of this approach.&lt;br&gt;
Its ESLint rules can flag a deprecated &lt;code&gt;sw-button&lt;/code&gt; and point you to &lt;code&gt;mt-button&lt;/code&gt;.&lt;br&gt;
Use the rules shipped with your installed Shopware version; this illustrates what the tooling can do today.&lt;/p&gt;

&lt;p&gt;The existing Shopware CLI project upgrade workflow already supports local upgrade preparation.&lt;br&gt;
The aim is to bring the 6.8 compatibility checks together in a unified diagnostic experience through Shopware CLI, making preparation easier for developers.&lt;br&gt;
As these building blocks develop through the rest of 2026, concrete feedback can help us improve them.&lt;/p&gt;

&lt;p&gt;Run PHPUnit, PHPStan and the available Administration checks as separate CI steps until the unified command is released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build compatibility into regular maintenance
&lt;/h2&gt;

&lt;p&gt;For plugin developers, a reliable CI pipeline is the foundation.&lt;br&gt;
Each new Shopware release provides an opportunity to run the checks, investigate failures and review fixes.&lt;br&gt;
Testing across the supported version range then provides the basis for releasing a compatible update.&lt;/p&gt;

&lt;p&gt;AI agents can help analyze failures and propose changes.&lt;br&gt;
Developers still need to review, test and approve them.&lt;br&gt;
Smaller batches, perhaps alongside each monthly release, are easier to inspect than a single large set of changes shortly before the major upgrade.&lt;br&gt;
Agency teams can use the same rhythm for custom project code.&lt;/p&gt;

&lt;p&gt;My recommendation is to get CI in place before handing changes to an agent.&lt;br&gt;
A small, tested fix each month is much easier to review than a pull request that rewrites the plugin just before 6.8.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the preparation steps
&lt;/h2&gt;

&lt;p&gt;You don’t need to wait for a Shopware 6.8 upgrade project to start preparing.&lt;br&gt;
Where your current Shopware version, extensions and hosting support it, upgrade PHP and the database in advance, then release compatible plugin updates and custom-code changes separately.&lt;br&gt;
Test each change before moving to the next one.&lt;br&gt;
That keeps the major upgrade smaller and makes problems easier to trace.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare PHP.&lt;/strong&gt;&lt;br&gt;
PHP 8.5 is the planned minimum for 6.8.&lt;br&gt;
Move only when the currently installed Shopware version, extensions and dependencies support it.&lt;br&gt;
Resolve Symfony deprecations on compatible current releases ahead of the planned move to Symfony 8.x or later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare the database.&lt;/strong&gt;&lt;br&gt;
The planned minimums are MySQL 8.4 or MariaDB 11.4, depending on the engine you use.&lt;br&gt;
Confirm current-release and hosting support, and test custom migrations before upgrading separately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Update plugins.&lt;/strong&gt;&lt;br&gt;
Prefer releases supporting both the current and target Shopware versions where available.&lt;br&gt;
Check vendor plans and identify gaps early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adapt custom code.&lt;/strong&gt;&lt;br&gt;
Address deprecations and review affected integrations, themes and components.&lt;br&gt;
Planned Twig 4 and Symfony UX Twig Component 3 changes also deserve attention, especially for custom extensions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate changed behavior.&lt;/strong&gt;&lt;br&gt;
Test relevant flags individually, keep the major-flag CI run, and verify customer journeys and integrations in staging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complete the major upgrade.&lt;/strong&gt;&lt;br&gt;
Bring the prepared components together and validate the full shop.&lt;br&gt;
Separating earlier changes makes unexpected results easier to trace.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Share the cases that need more support
&lt;/h2&gt;

&lt;p&gt;Early testing can improve both your own plan and the tools available to everyone.&lt;br&gt;
Share a reproducible example, the Shopware versions you need to support, and what makes a migration difficult in Discord &lt;strong&gt;&lt;a href="https://discord.com/channels/1308047705309708348/1557367351471644732" rel="noopener noreferrer"&gt;#feedback-6_8&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The greatest opportunity to influence the approach is before scope settles and the first release candidate arrives.&lt;br&gt;
Start with one project, try the available checks and bring the findings back to the team.&lt;br&gt;
Those small steps can turn preparation into steady progress and reduce surprises when it is time to upgrade.&lt;/p&gt;

&lt;p&gt;A useful report might show a plugin that needs two different class declarations to support old and new interfaces, or a template warning that flags an intentional compatibility fallback.&lt;br&gt;
Include a small code example, the failing check and the versions you need to keep working.&lt;br&gt;
These are the kinds of cases I’d like you to share: they show us where to focus our efforts to make Shopware 6.8 the smoothest upgrade to date.&lt;/p&gt;

</description>
      <category>shopware</category>
      <category>php</category>
    </item>
    <item>
      <title>A new security reality for commerce</title>
      <dc:creator>Jonas Elfering</dc:creator>
      <pubDate>Tue, 27 Oct 2026 10:07:08 +0000</pubDate>
      <link>https://dev.to/shopware/a-new-security-reality-for-commerce-8k1</link>
      <guid>https://dev.to/shopware/a-new-security-reality-for-commerce-8k1</guid>
      <description>&lt;p&gt;&lt;em&gt;As threats evolve, speed matters more—and everyone in the Shopware ecosystem has a part to play.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The security landscape is changing: AI-assisted research is helping researchers and attackers find weaknesses and develop working exploits faster. We are expanding our security work, hardening Shopware, and improving how security fixes reach shops. Keeping shops protected is a shared effort: our work has the greatest impact when merchants make security an operational priority, agencies deploy updates promptly, and extension partners keep their code secure and dependencies up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are doing at Shopware
&lt;/h2&gt;

&lt;p&gt;Publishing security patches remains an essential part of our work. But addressing individual vulnerabilities is only one part of adapting to this situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expanding the security team
&lt;/h3&gt;

&lt;p&gt;We are expanding our security team to strengthen both proactive security work and our response to findings.&lt;/p&gt;

&lt;p&gt;This includes reviewing designs and code, improving automated checks, and helping development teams address recurring root causes.&lt;/p&gt;

&lt;p&gt;We also want to improve report triage and fix coordination, turning lessons from findings into practical guidance and safer development patterns across Shopware.&lt;/p&gt;

&lt;p&gt;If you want to help improve the security of Shopware and its ecosystem, have a look at our &lt;a href="https://careers.shopware.com" rel="noopener noreferrer"&gt;open positions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking for vulnerabilities proactively
&lt;/h3&gt;

&lt;p&gt;We continue to combine regular, externally conducted penetration testing with our existing &lt;a href="https://www.shopware.com/en/contact/security-reporting/" rel="noopener noreferrer"&gt;bug-bounty program&lt;/a&gt;, through which researchers can responsibly report vulnerabilities. Additionally, we are actively scanning for vulnerabilities using the most capable AI models available to us.&lt;/p&gt;

&lt;p&gt;These approaches complement each other. External researchers bring different perspectives, while AI-assisted analysis helps us examine more code and possible interactions. A model's output still needs to be validated: a plausible description is not proof that a vulnerability exists, and a scan that finds nothing is not proof that the code is secure.&lt;/p&gt;

&lt;p&gt;Therefore, we treat AI as an additional tool for finding problems, not as a replacement for security engineering or the researchers who work with us.&lt;/p&gt;

&lt;p&gt;We already use the latest advances in AI to scan incoming code. At the same time, we need to find and fix weaknesses in existing code, prioritizing findings according to their potential impact and the risk they pose to shops.&lt;/p&gt;

&lt;p&gt;The likely outcome is more security fixes and more frequent security releases. That is a positive consequence of finding and addressing weaknesses that might previously have remained undetected. Agencies, merchants, and extension partners should be prepared for this increased update cadence and have a process in place to apply security fixes promptly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making exploit chains harder to complete
&lt;/h3&gt;

&lt;p&gt;Fixing the initial vulnerability is important, but we also need to look at what an attacker could do after gaining an initial foothold.&lt;/p&gt;

&lt;p&gt;For example, an attacker who gains limited access should not automatically be able to turn that access into broader control over the shop. That depends on the boundaries between different parts of the system and on which operations remain possible with the access they have gained.&lt;/p&gt;

&lt;p&gt;We will further harden these boundaries to block common exploit chains and limit the impact of an initial compromise. This does not make the original vulnerability acceptable or remove the need to patch it. It adds protection for cases where one defense has already failed.&lt;/p&gt;

&lt;p&gt;We are also improving the forensic data available to help detect and investigate attacks. The goal is to make it easier to identify exploitation attempts while they are happening and, if an incident occurs, reconstruct what happened and understand which parts of the shop were affected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making security fixes easier to apply
&lt;/h3&gt;

&lt;p&gt;Publishing a security fix gives merchants a way to protect their shops, but it also gives attackers clues about the vulnerability. Getting that protection into production quickly is therefore just as important as making it available. Once a vulnerability is disclosed, exploitation attempts can follow quickly and may be automated. Shops affected by that vulnerability need a process that gets protection into production promptly.&lt;/p&gt;

&lt;p&gt;We are rethinking how we deliver security patches so that securing a shop requires less time and coordination. Agencies and merchants need to be able to apply protection quickly without turning every security update into a larger upgrade project.&lt;/p&gt;

&lt;p&gt;We are strengthening the testing, compatibility, and reliability of security updates, building on what we have learned from previous releases. The goal is to help merchants and agencies apply protection quickly and with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the security landscape is changing
&lt;/h2&gt;

&lt;p&gt;Security updates have always been part of running an online shop. However, the number of security reports we receive and the pace at which attackers act on disclosed vulnerabilities have changed considerably. That has consequences for how we develop Shopware, but also for how extensions are maintained and production shops are operated.&lt;/p&gt;

&lt;p&gt;Our own reporting data helps explain why we are strengthening these efforts. The following chart shows Shopware 6 bug-bounty reports and security advisory records created over time.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxh7ldgwcq4mfgerol1bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxh7ldgwcq4mfgerol1bi.png" alt=" " width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These figures include unpublished advisory records and do not represent distinct confirmed vulnerabilities; reports and advisories do not map one to one.&lt;/p&gt;

&lt;p&gt;The increase in findings does not tell us how many shops are affected or compromised. More reports do not, by themselves, show that the software has become less secure. They show that more potential weaknesses are being reported and investigated. AI-assisted research can uncover vulnerabilities that likely remained undetected before these tools became available.&lt;/p&gt;

&lt;p&gt;However, those tools are available to attackers as well. If a security researcher can find an issue with AI, we have to assume that a bad actor may independently find it too. Responsible disclosure therefore cannot be treated as a period in which only the researcher and the vendor know about a vulnerability. The time available to investigate, fix, and deploy protection may be much shorter, even before an advisory is published.&lt;/p&gt;

&lt;p&gt;Once an advisory is public, we already see automated exploitation attempts within days. So both parts matter: acting quickly on incoming reports and getting the resulting protection into production promptly.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is an industry-wide challenge
&lt;/h2&gt;

&lt;p&gt;AI-assisted research is changing both how many weaknesses are found and how readily they can be turned into working attacks.&lt;/p&gt;

&lt;p&gt;The recent &lt;a href="https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/" rel="noopener noreferrer"&gt;PHP security release&lt;/a&gt; illustrates this: &lt;a href="https://calif.io/php2shell/" rel="noopener noreferrer"&gt;PHP2Shell researchers&lt;/a&gt; used AI to help demonstrate exploitation of a long-standing weakness whose impact had remained unproven.&lt;/p&gt;

&lt;p&gt;Software does not have to change for its practical exposure to change—the tools available to researchers and attackers can change around it.&lt;/p&gt;

&lt;p&gt;Other projects are seeing a similar increase in security findings. In a &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7517910546206179328/" rel="noopener noreferrer"&gt;recent post about Symfony and Twig&lt;/a&gt;, Fabien Potencier described a sharp rise in reports and advisories alongside the growing use of AI in security research.&lt;/p&gt;

&lt;p&gt;These are just a few examples of what has happened across the wider ecosystem in the last month alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Magento and Adobe Commerce:&lt;/strong&gt; &lt;a href="https://sansec.io/research/blockstrap-magento-rce" rel="noopener noreferrer"&gt;Sansec reproduced four BlockStrap exploit routes within 24 hours of Adobe’s patch&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress:&lt;/strong&gt; &lt;a href="https://patchstack.com/articles/cve-2026-87902-attackers-started-probing-wordpress-sites-hours-after-the-patch/" rel="noopener noreferrer"&gt;Patchstack observed probing within hours of a security patch, followed by exploitation attempts the next day&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify's app ecosystem:&lt;/strong&gt; &lt;a href="https://www.logbase.io/selleasy/updates/incident-20261013" rel="noopener noreferrer"&gt;Selleasy reported an incident involving injected scripts that redirected customers to phishing pages&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These examples illustrate a shared challenge across platforms and their ecosystems, not a comparison of vendors' security records.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for extension partners
&lt;/h2&gt;

&lt;p&gt;A shop consists of more than Shopware core. Extensions can handle customer data, expose API routes, process files, or change what happens during checkout. Their security is therefore part of the security of the shop itself.&lt;/p&gt;

&lt;p&gt;In successful attacks we have investigated, third-party extensions have already been used as part of the attack. This is not just a theoretical concern that might become relevant at some point in the future.&lt;/p&gt;

&lt;p&gt;If you maintain extensions, start reviewing them now. A useful starting point is to look at where data enters your extension and what a caller is allowed to do with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are permissions and ownership checked on the server, rather than only in the Administration interface?&lt;/li&gt;
&lt;li&gt;Is untrusted input handled safely before it reaches database queries, templates, or file operations?&lt;/li&gt;
&lt;li&gt;Are the libraries your extension depends on current and free of known vulnerabilities?&lt;/li&gt;
&lt;li&gt;Could limited access to one feature be combined with another feature to gain more access than intended?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI-assisted reviews can help with this work, but the findings still need to be checked against the actual behavior of the extension.&lt;/p&gt;

&lt;p&gt;We are also exploring how additional tools and processes could help extension partners identify vulnerabilities, coordinate findings, and deliver security fixes more easily. In parallel, partners should start reviewing their code and addressing the issues they find.&lt;/p&gt;

&lt;h2&gt;
  
  
  What agencies and merchants should do
&lt;/h2&gt;

&lt;p&gt;For practical guidance on supported versions, infrastructure security, and extension management, see our &lt;a href="https://developer.shopware.com/docs/guides/hosting/installation-updates/security-best-practices/" rel="noopener noreferrer"&gt;Security Best Practices guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Know which responsibilities your hosting model covers
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://docs.shopware.com/en/shopware-6-en/in-the-cloud" rel="noopener noreferrer"&gt;Shopware SaaS&lt;/a&gt;, we take care of securing and maintaining the platform and its infrastructure, including automatically applying platform updates and security patches.&lt;/p&gt;

&lt;p&gt;You do not need to manage servers or install the Security Plugin yourself.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://developer.shopware.com/docs/products/paas/shopware/" rel="noopener noreferrer"&gt;Shopware PaaS Native&lt;/a&gt;, we manage the underlying infrastructure, including servers, networking, managed databases, and platform backups, and provide a web application firewall enabled by default.&lt;/p&gt;

&lt;p&gt;You or your agency still manage the application: testing and deploying Shopware, Security Plugin, extension, and dependency updates, and securing your custom code.&lt;/p&gt;

&lt;p&gt;You also select a supported, compatible PHP version in the application configuration.&lt;/p&gt;

&lt;p&gt;Runtime extension management is already disabled; extensions are managed through Composer and deployments.&lt;/p&gt;

&lt;p&gt;Managed infrastructure and a firewall do not replace prompt application updates.&lt;/p&gt;

&lt;p&gt;In both models, merchants remain responsible for their configuration, access and user management, and the security of external apps and integrations they operate or connect.&lt;/p&gt;

&lt;p&gt;For self-hosted shops, infrastructure security also needs to be managed by you or your hosting provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the Security Plugin installed and up to date
&lt;/h3&gt;

&lt;p&gt;We are changing our recommendation for self-hosted shops.&lt;/p&gt;

&lt;p&gt;Previously, we recommended the &lt;a href="https://store.shopware.com/en/swag136939272659f/shopware-6-security-plugin.html" rel="noopener noreferrer"&gt;Shopware Security Plugin&lt;/a&gt; primarily when a platform update could not be deployed immediately.&lt;/p&gt;

&lt;p&gt;We now recommend keeping it installed, activated, and up to date with a version compatible with the shop, even when the platform itself is already fully up to date. See our &lt;a href="https://developer.shopware.com/docs/guides/hosting/installation-updates/security-best-practices/security-plugin.html" rel="noopener noreferrer"&gt;Security Plugin guide&lt;/a&gt; for the updated recommendation, compatibility, and installation instructions.&lt;/p&gt;

&lt;p&gt;Having the plugin in place makes it easier to apply the next security update promptly.&lt;/p&gt;

&lt;p&gt;Make sure your shop runs a Shopware version that is still receiving security support, as shown in our &lt;a href="https://developer.shopware.com/release-notes/" rel="noopener noreferrer"&gt;release policy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The plugin changelog lists the issues fixed by each plugin release.&lt;/p&gt;

&lt;p&gt;The Security Plugin provides a fast way to apply supported fixes to known vulnerabilities in Shopware’s own code, especially when a complete platform update cannot be deployed immediately. It does not replace updates to PHP, third-party dependencies, extensions, or Shopware itself. Additional hardening and broader security improvements are delivered in regular Shopware releases and are usually not backported through the plugin. Keeping Shopware and its extensions up to date therefore remains essential to benefit from those additional layers of protection.&lt;/p&gt;

&lt;p&gt;When a relevant Security Plugin release becomes available, update it as soon as possible. Do not defer that work to the next weekly or monthly maintenance window. We see automated exploitation attempts within days of advisory publication, so a process that routinely leaves shops unprotected for weeks does not match the threat.&lt;/p&gt;

&lt;p&gt;That does not mean skipping validation. It means having a process in place that allows an urgent security update to be checked and deployed promptly, separately from larger feature releases or routine maintenance.&lt;/p&gt;

&lt;p&gt;If you suspect your shop has already been compromised, applying a security update alone does not establish that it is clean; contact your hosting provider and agency first, and follow our &lt;a href="https://developer.shopware.com/docs/guides/hosting/installation-updates/security-best-practices/incident-response.html" rel="noopener noreferrer"&gt;guide to responding to a cyberattack&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat security communication as an operational task
&lt;/h3&gt;

&lt;p&gt;When we send a security notice, it needs to reach someone who can act on it. Receiving an email is not the same as protecting the shop.&lt;/p&gt;

&lt;p&gt;Agencies and merchants should agree on who receives those notices, who checks whether a shop is affected, and who is responsible for deploying the required protection. The handover between merchant, agency, and hosting provider should already be clear before an urgent update arrives.&lt;/p&gt;

&lt;p&gt;We also have a responsibility to make our communication clear and actionable. Security notices should explain what is affected, what action is required, and how urgently it needs to happen. Please treat that communication seriously and follow the instructions for the specific issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Adapt the processes around the software too
&lt;/h2&gt;

&lt;p&gt;Shopware is adapting how we find vulnerabilities, harden the platform, and deliver protection.&lt;/p&gt;

&lt;p&gt;Here are the steps the ecosystem can take now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check that your Shopware version is still receiving security support using our &lt;a href="https://developer.shopware.com/release-notes/" rel="noopener noreferrer"&gt;release policy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Keep a compatible Security Plugin installed, activated, and up to date.&lt;/li&gt;
&lt;li&gt;Agree who receives security notices, assesses whether your shop is affected, and deploys urgent updates.&lt;/li&gt;
&lt;li&gt;If you maintain extensions, review your code for security weaknesses and deliver fixes promptly.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>shopware</category>
    </item>
    <item>
      <title>When `@deprecated` cries wolf: Making Shopware’s next major upgrades easier</title>
      <dc:creator>Jonas Elfering</dc:creator>
      <pubDate>Tue, 25 Aug 2026 06:48:24 +0000</pubDate>
      <link>https://dev.to/shopware/when-deprecated-cries-wolf-making-shopwares-next-major-upgrades-easier-983</link>
      <guid>https://dev.to/shopware/when-deprecated-cries-wolf-making-shopwares-next-major-upgrades-easier-983</guid>
      <description>&lt;p&gt;When PHPStan reports that your extension calls a deprecated method, the expected next step is quite clear: find the replacement and migrate your code.&lt;/p&gt;

&lt;p&gt;But what if there is no replacement?&lt;/p&gt;

&lt;p&gt;Consider &lt;code&gt;Context::scope()&lt;/code&gt;. Previously, its planned change for Shopware 6.8 was announced like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @deprecated tag:v6.8.0 - reason:new-optional-parameter - parameter $states will be added
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;\Closure&lt;/span&gt; &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mixed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Static analysis sees &lt;code&gt;@deprecated&lt;/code&gt; and reports every call to the method. However, the method is not going away. A new &lt;em&gt;optional&lt;/em&gt; parameter will be added, so existing calls will continue to work without any changes.&lt;/p&gt;

&lt;p&gt;There is no alternative API to migrate to and no warning to resolve. In this situation, &lt;code&gt;@deprecated&lt;/code&gt; is effectively crying wolf.&lt;/p&gt;

&lt;p&gt;With Shopware 6.7.14.0, we are changing how these planned API changes are communicated. Real deprecations remain deprecations. Other backward-compatibility changes are now described with dedicated, structured PHP attributes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The immediate result is less noise for extension developers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Additionally, the new attributes give us a foundation for preparing extensions for Shopware 6.8 - and future major releases - before those releases arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Shopware now uses two different signals for two different purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@deprecated&lt;/code&gt; means that an API is obsolete and will be removed or replaced. Extension developers need to migrate away from it.&lt;/li&gt;
&lt;li&gt;BC-change attributes describe a future change to an API that remains available, such as a new parameter, a narrower return type, or a class becoming final.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The attributes also distinguish between changes that affect code &lt;em&gt;calling&lt;/em&gt; an API and changes that affect classes &lt;em&gt;extending&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;This means deprecation warnings become trustworthy and actionable again, while planned contract changes carry enough structured information for PHPStan, Rector, IDEs, and other tools to reason about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  We were asking &lt;code&gt;@deprecated&lt;/code&gt; to do two different jobs
&lt;/h2&gt;

&lt;p&gt;The commonly understood meaning of &lt;code&gt;@deprecated&lt;/code&gt; in the PHP ecosystem is quite narrow: this API should no longer be used. IDEs may strike it through, and static-analysis tools warn when application code still depends on it.&lt;/p&gt;

&lt;p&gt;This is useful behaviour. If a method is going away, extension developers should know about it as early as possible.&lt;/p&gt;

&lt;p&gt;Shopware, however, also used &lt;code&gt;@deprecated&lt;/code&gt; as an internal planning mechanism for the next major version. A &lt;code&gt;reason:*&lt;/code&gt; marker could announce that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A return type will become narrower.&lt;/li&gt;
&lt;li&gt;A parameter type will change.&lt;/li&gt;
&lt;li&gt;A new optional parameter will be added.&lt;/li&gt;
&lt;li&gt;A class will become final or internal.&lt;/li&gt;
&lt;li&gt;A method’s visibility will change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all relevant backward-compatibility changes, but they are not all deprecations.&lt;/p&gt;

&lt;p&gt;In many cases, the annotated method remains the correct API to use. Whether an extension needs to change anything depends on &lt;em&gt;how&lt;/em&gt; it uses that API. Calling a method and overriding it in a subclass are two very different forms of dependency.&lt;/p&gt;

&lt;p&gt;The standard &lt;code&gt;@deprecated&lt;/code&gt; signal cannot express that distinction. It also cannot express a future type, parameter name, default value, or affected audience in a machine-readable form. Static-analysis tools should not be expected to understand Shopware-specific prose inside a PHPDoc annotation.&lt;/p&gt;

&lt;p&gt;We could have recommended that every extension add more ignore patterns. But a blanket ignore would also hide genuine deprecations (the warnings where an extension developer really does need to act).&lt;/p&gt;

&lt;p&gt;The problem was therefore not PHPStan being too strict. We were giving it the wrong signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A more precise model for backward-compatibility changes
&lt;/h2&gt;

&lt;p&gt;The new attributes live in the &lt;code&gt;Shopware\Core\Framework\Deprecation\BCChange&lt;/code&gt; namespace. There are currently 15 concrete attributes covering changes to types, parameters, visibility, inheritance, and exception contracts.&lt;/p&gt;

&lt;p&gt;Every attribute belongs to one or both of two audiences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CallSiteCompatibilityChange&lt;/code&gt; describes a change that may affect code calling a method.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ExtenderCompatibilityChange&lt;/code&gt; describes a change that may affect a subclass or method override.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, narrowing a parameter type affects callers that currently pass a value outside the future type. Widening that parameter type does not affect callers, but it may make an overriding method incompatible.&lt;/p&gt;

&lt;p&gt;Return types work in the opposite direction because PHP return types are covariant. A narrower return type does not break callers, but subclasses overriding the method may need to adjust their declaration.&lt;/p&gt;

&lt;p&gt;That is also why the attributes have directional names such as &lt;code&gt;ReturnTypeNarrowing&lt;/code&gt;, &lt;code&gt;ReturnTypeWidening&lt;/code&gt;, &lt;code&gt;ParameterTypeNarrowing&lt;/code&gt;, and &lt;code&gt;ParameterTypeWidening&lt;/code&gt;. The direction tells us who may be affected.&lt;/p&gt;

&lt;p&gt;Some changes affect both sides. If a method becomes internal, for example, code calling it and code extending it may both need to stop relying on it.&lt;/p&gt;

&lt;p&gt;Whether you need to act is therefore no longer answered with a generic “this method is deprecated.” It is answered by the concrete change and the way your extension uses the API.&lt;/p&gt;

&lt;p&gt;The design started as a &lt;a href="https://github.com/shopware/shopware/discussions/17798" rel="noopener noreferrer"&gt;community-driven RFC&lt;/a&gt; and was refined through discussion and implementation feedback. It was introduced through the &lt;a href="https://github.com/shopware/shopware/pull/17979" rel="noopener noreferrer"&gt;attribute family&lt;/a&gt;, &lt;a href="https://github.com/shopware/shopware/pull/17985" rel="noopener noreferrer"&gt;PHPStan enforcement&lt;/a&gt;, and the &lt;a href="https://github.com/shopware/shopware/pull/17992" rel="noopener noreferrer"&gt;migration of existing planning annotations&lt;/a&gt;; &lt;code&gt;ParameterDefaultValueChange&lt;/code&gt; followed as a &lt;a href="https://github.com/shopware/shopware/pull/18699" rel="noopener noreferrer"&gt;later refinement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How extension developers can use the new information
&lt;/h2&gt;

&lt;p&gt;The attributes themselves are marked &lt;code&gt;@internal&lt;/code&gt;. They are not intended as a new extension API that plugins should instantiate or add to their own code.&lt;/p&gt;

&lt;p&gt;What extension developers can rely on is the meaning of the announcement on a Shopware Core API: what changes, when it changes, and whether callers or extenders may be affected.&lt;/p&gt;

&lt;p&gt;Let us return to &lt;code&gt;Context::scope()&lt;/code&gt;. Its planned change is now expressed as structured metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;NewOptionalParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'v6.8.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;parameterName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'states'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;parameterType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'array'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;defaultValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;\Closure&lt;/span&gt; &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mixed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For callers, there is nothing to do. The new parameter is optional, so existing calls remain valid.&lt;/p&gt;

&lt;p&gt;For a class overriding the method, the situation is different. The override can add the optional parameter today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;\Closure&lt;/span&gt; &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$states&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mixed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional optional parameters are compatible with the current parent declaration. Once Shopware 6.8 adds the parameter, the override remains compatible. The extension can therefore prepare without a Shopware version check or two separate implementations.&lt;/p&gt;

&lt;p&gt;Return-type narrowing provides another example. &lt;code&gt;Field::setFlags()&lt;/code&gt; currently returns &lt;code&gt;self&lt;/code&gt;, but will return &lt;code&gt;static&lt;/code&gt; in Shopware 6.8:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ReturnTypeNarrowing(version: 'v6.8.0', newType: 'static')]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Flag&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$flags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Callers are not affected because every &lt;code&gt;static&lt;/code&gt; value is also a valid &lt;code&gt;self&lt;/code&gt; value. An overriding method can already declare &lt;code&gt;static&lt;/code&gt;, though, because PHP permits covariant return types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Flag&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$flags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;static&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, one declaration works with both the current version and the future one.&lt;/p&gt;

&lt;p&gt;Parameter renames mainly affect named arguments. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ParameterNameChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'v6.8.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;parameterName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'filename'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'fileName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A call using &lt;code&gt;filename:&lt;/code&gt; will break after the rename. Switching to positional arguments works with both parameter names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DomainVerificationRequestStruct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://developer.shopware.com/docs/resources/guidelines/code/backward-compatibility.html#announced-api-changes-bc-change-attributes" rel="noopener noreferrer"&gt;backward-compatibility guide&lt;/a&gt; contains the full attribute inventory and a per-change guide explaining which adaptations can already be made without dropping compatibility with the current Shopware version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static metadata does not replace runtime deprecations
&lt;/h2&gt;

&lt;p&gt;Not every compatibility problem should be left entirely to static analysis.&lt;/p&gt;

&lt;p&gt;If incompatible legacy usage can be detected when the method runs, Shopware still emits a conditional runtime deprecation. For example, when a parameter will be narrowed from &lt;code&gt;?string&lt;/code&gt; to &lt;code&gt;string&lt;/code&gt;, Core can detect that a caller still passes &lt;code&gt;null&lt;/code&gt; and trigger &lt;code&gt;Feature::triggerDeprecationOrThrow()&lt;/code&gt; for that call.&lt;/p&gt;

&lt;p&gt;This is more precise than deprecating the entire method. Callers already passing a string receive no warning, while callers depending on the behaviour that will break in 6.8 get an actionable message.&lt;/p&gt;

&lt;p&gt;Other changes cannot be detected reliably at runtime. A method cannot generally know that an extension overrides it with a future-incompatible declaration. A class becoming final cannot warn only the subclasses that matter without also creating noise for test doubles and other legitimate current usage.&lt;/p&gt;

&lt;p&gt;For those changes, structured static information is the better tool.&lt;/p&gt;

&lt;p&gt;Shopware’s PHPStan rules validate these announcements inside Core. They verify that parameter names and types resolve, that the announced state differs from the current state, and that runtime deprecations exist where incompatible use can be detected. Attributes also fail validation after their target version has arrived, so they cannot silently become stale documentation.&lt;/p&gt;

&lt;p&gt;A separate rule prevents the old &lt;code&gt;@deprecated reason:*&lt;/code&gt; planning markers from being reintroduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  What becomes possible next
&lt;/h2&gt;

&lt;p&gt;The first benefit for extension projects requires no additional tooling: after updating to Shopware 6.7.14.0, Core’s internal planning notes no longer appear as generic deprecation errors.&lt;/p&gt;

&lt;p&gt;The structured metadata also allows us to go further.&lt;/p&gt;

&lt;p&gt;A forward-looking &lt;a href="https://github.com/shopware/phpstan-shopware/pull/43" rel="noopener noreferrer"&gt;proof of concept in &lt;code&gt;phpstan-shopware&lt;/code&gt;&lt;/a&gt; explores an opt-in future-compatibility ruleset. Instead of analysing only the declarations available today, it can interpret the announced contracts and check whether extension code is already compatible with the next major version.&lt;/p&gt;

&lt;p&gt;Such a ruleset can, for example, find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calls that pass values outside a future narrowed parameter type.&lt;/li&gt;
&lt;li&gt;Named arguments using a parameter’s old name.&lt;/li&gt;
&lt;li&gt;Overrides missing a future optional parameter.&lt;/li&gt;
&lt;li&gt;Classes extending something that will become final or internal.&lt;/li&gt;
&lt;li&gt;Code that does not handle a future widened return type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This remains forward-looking work and is deliberately separate from the default PHPStan rules. Extension projects should be able to opt into stricter future-compatibility analysis when it fits their support strategy.&lt;/p&gt;

&lt;p&gt;The same metadata could be used as input for custom Rector rules. Instead of parsing free-form PHPDoc text, a rule can read a specific change type and its payload, then apply a focused migration. IDE inspections, LSP diagnostics, compatibility reports, and upgrade-note generators are other possible consumers.&lt;/p&gt;

&lt;p&gt;The important part is that we now have reliable input on which such tools can be built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop ignoring deprecations
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;@deprecated&lt;/code&gt; cries wolf often enough, developers stop listening. That was the real cost of using the same signal for both removals and internal BC planning: every false alarm made the next genuine deprecation easier to ignore.&lt;/p&gt;

&lt;p&gt;With the two concerns now separated, that excuse is gone. Extension developers should take three concrete actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove broad deprecation suppressions
&lt;/h3&gt;

&lt;p&gt;An &lt;code&gt;@deprecated&lt;/code&gt; annotation on a public Shopware Core API is actionable. The API will be removed or replaced, and extensions using it need to follow the documented migration path.&lt;/p&gt;

&lt;p&gt;Broad ignore patterns that hide Shopware deprecations should therefore be removed. They now risk hiding work that must be completed before the next major release.&lt;/p&gt;

&lt;p&gt;Review the remaining deprecation reports in your extension, identify the documented replacement, and treat the migration as required maintenance rather than optional cleanup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check BC-change attributes on the APIs you use
&lt;/h3&gt;

&lt;p&gt;Removing false warnings is only one half of the improvement. Extension developers also need to pick up the new BC-change signals.&lt;/p&gt;

&lt;p&gt;When an API used by your extension carries a BC-change attribute, check whether it applies to call sites, extenders, or both. Then compare the announced change with how your extension uses that API.&lt;/p&gt;

&lt;p&gt;The attributes do not make an extension forward-compatible by themselves. They provide the information needed to do so. If they are ignored until the major upgrade, the incompatibilities will still arrive all at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adopt forward-compatible declarations early
&lt;/h3&gt;

&lt;p&gt;Many announced changes can be handled before the next major release without dropping support for the current Shopware version.&lt;/p&gt;

&lt;p&gt;The examples above show the concrete benefit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An override can add an announced optional parameter today.&lt;/li&gt;
&lt;li&gt;An override can adopt an announced narrower return type today.&lt;/li&gt;
&lt;li&gt;A call site can stop relying on a parameter name that will change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not merely preparations written down for later. They are changes extension developers can make now. One implementation can remain backward-compatible with the current Shopware version while already being compatible with the announced future contract.&lt;/p&gt;

&lt;p&gt;Make these checks part of regular extension maintenance and static analysis instead of postponing them until the major upgrade. That turns one disruptive migration into a series of smaller, safer changes.&lt;/p&gt;

&lt;p&gt;Shopware 6.8 is the first major upgrade to benefit from this model, but it will not be the last. Future major changes can use the same structured announcements, while PHPStan, Rector, and other tools can become increasingly effective at turning them into concrete migration work.&lt;/p&gt;

&lt;p&gt;The goal is to stop &lt;code&gt;@deprecated&lt;/code&gt; from crying wolf. When it appears now, treat it as a warning that demands action. When a BC-change attribute appears, use the head start it provides.&lt;/p&gt;

</description>
      <category>shopware</category>
      <category>php</category>
      <category>deprecation</category>
    </item>
    <item>
      <title>Adding and maintaining new languages in Shopware is now dead simple</title>
      <dc:creator>Marco Steinhaeuser</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:52:30 +0000</pubDate>
      <link>https://dev.to/shopware/adding-and-maintaining-new-languages-in-shopware-is-now-dead-simple-503p</link>
      <guid>https://dev.to/shopware/adding-and-maintaining-new-languages-in-shopware-is-now-dead-simple-503p</guid>
      <description>&lt;p&gt;With Shopware v6.7.3.0, we introduced a very interesting feature for those of you who have to deal with languages and translations in your online shops. As we ported the functionality of the formerly known &lt;a href="https://store.shopware.com/en/swag338126230916f/shopware-language-pack.html" rel="noopener noreferrer"&gt;Shopware language pack&lt;/a&gt; into the core of Shopware, installing and maintaining languages in Shopware became dead-simple. In this blog post, you will learn how it works and what the limits of this modus operandi are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation platform on Crowdin
&lt;/h2&gt;

&lt;p&gt;The basis is our &lt;a href="https://translate.shopware.com" rel="noopener noreferrer"&gt;translation platform&lt;/a&gt; with the generous help of Crowdin. All newly appeared language keys (AKA "snippets") in the Shopware core will automatically be available in Crowdin, followed by an automatic translation utilising &lt;a href="https://aws.amazon.com/translate/" rel="noopener noreferrer"&gt;Amazon Translate&lt;/a&gt;. Crowdin shows how much of a language is human-translated via the colour of the status bar in the language overview: Blue means machine translated, green means human translated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxd0guqyxeri82j5h60h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxd0guqyxeri82j5h60h.png" alt="screenshot of progress bars in Crowdin" width="758" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All translations will be synchronised daily to the repository &lt;a href="https://github.com/shopware/translations/" rel="noopener noreferrer"&gt;github.com/shopware/translations/&lt;/a&gt;, after synchronisation they can be used as shown in the following steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a new translation to Shopware
&lt;/h2&gt;

&lt;p&gt;Shopware comes with just three pre-installed languages: English – British and American – plus German. Let's say you want to extend your online shop with Spanish like it is spoken in Spain (es-ES).&lt;/p&gt;

&lt;p&gt;Login to your server via the terminal, navigate to the location of your .env file, and execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ php bin/console translation:install --locales=es-ES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that, Spanish is installed and already activated in your Shopware instance. All you have to do now is login to your administration panel and adjust the URL for your new language, for example &lt;strong&gt;&lt;a href="https://example.com/es/" rel="noopener noreferrer"&gt;https://example.com/es/&lt;/a&gt;&lt;/strong&gt; (replace example.com with your domain). When you navigate to this URL, you'll now see Spanish translations all over – well, except for your products and custom text. 😉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mwgmg283gsdlia8kyvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mwgmg283gsdlia8kyvb.png" alt="part of Shopware storefront saying that there are no products in spanish" width="800" height="30"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintaining translations
&lt;/h2&gt;

&lt;p&gt;Let's go a step further and update your new translation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ php bin/console translation:update --locales=es-ES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, you will fetch the latest language keys, no matter if used or not, and also the latest human approved translations by the community.&lt;/p&gt;

&lt;p&gt;There are even more commands and options available. Read more about it in the &lt;a href="https://developer.shopware.com/docs/concepts/translations/built-in-translation-system.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to have your very own behaviour for special language keys in your online shop, there is no need to fight about the correct phrase on our translation platform. Think of cart vs. basket vs. trolley, for example. In this case, simply make use of &lt;strong&gt;Shopware admin » General » Languages » Manage Snippets&lt;/strong&gt;. These changes will be stored in the database and have first loading priority. This way, your newly installed Spanish translation set stays update-safe, and you can have your very own style without bothering others. However, other community members still can make use of it.&lt;/p&gt;

&lt;p&gt;The coolest advantage of these CLI commands is that you could even use the update method described above in a cron job or even in your continuous integration setup – at least for staging: stay up-to-date with the latest community corrections in this translation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A bit off-topic and out of scope for this blog post, but I don't want to miss pointing you to this great translation QA-tool for your CI: &lt;a href="https://github.com/boxblinkracer/phpunuhi" rel="noopener noreferrer"&gt;https://github.com/boxblinkracer/phpunuhi&lt;/a&gt; – indispensable for bigger online shops with any quality approach, isn't it? Especially, as it was developed by one of my best mates in the community, &lt;a href="https://github.com/boxblinkracer/" rel="noopener noreferrer"&gt;@boxblinkracer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Limits and disclaimer
&lt;/h2&gt;

&lt;p&gt;Please note that this blog post is about translations of the core language keys / snippets for the storefront as well as the administration panel of your Shopware instance. No product information nor any other database entries have automatically been translated.&lt;/p&gt;

&lt;p&gt;With the method described above, you are installing translations made by the community. Shopware can not give any guarantee on the quality nor the content of these translations. We cannot proofread languages we do not speak, which means there is the possibility of a bad actor adding wrong or even offensive text into translations. If you gain knowledge of it, &lt;a href="https://chat.shopware.com" rel="noopener noreferrer"&gt;please inform us&lt;/a&gt; as soon as possible to help keeping the quality of the translations high. Also, please avoid the automatic update method in production and only use it on staging for this reason.&lt;/p&gt;

&lt;p&gt;What I have described here will only work for translations available on &lt;a href="https://translate.shopware.com" rel="noopener noreferrer"&gt;translate.shopware.com&lt;/a&gt;. There are two other methods if you need a language not available through the Crowdin setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;(Preferred) &lt;a href="https://chat.shopware.com" rel="noopener noreferrer"&gt;Get in touch&lt;/a&gt; and request another language to be added to our Crowdin instance. We can quickly provide at least an automated translation, which will – over time, and depending on the quality of the automation – be approved by a human being.&lt;/li&gt;
&lt;li&gt;Create your own plugin on the basis of the &lt;a href="https://github.com/shopware/SwagLanguagePack" rel="noopener noreferrer"&gt;Shopware language plugin&lt;/a&gt;. This way, you have full ownership, without any community connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Last but not least: We will deliver any language available on &lt;a href="https://translate.shopware.com" rel="noopener noreferrer"&gt;translate.shopware.com&lt;/a&gt;, be it LTR (left to right) or RTL (right to left, like Arabic, for example), the way described above knowing that there might be some display glitches. This is because we rely on you, the community. You as native speakers have more experience in properly solving this topic in no time as we might still struggle in planning, sorting it out, resolving and testing it. Looking forward to a proper solution, best as a free/libre and open source plugin for Shopware. Don't hesitate to &lt;a href="https://chat.shopware.com" rel="noopener noreferrer"&gt;ping us&lt;/a&gt; once you're done with it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Send your translators to &lt;a href="https://translate.shopware.com" rel="noopener noreferrer"&gt;translate.shopware.com&lt;/a&gt; for approving existing auto-translations, correcting them and let the remaining language keys be translated for the benefit of the whole community.&lt;/p&gt;

&lt;p&gt;We are planning to publish a so called "in-context translation": There will be a demo Shopware instance that translators can use to see the context of what they are translating – everything connected to Crowdin. Once this is finished, we will announce it in this channel as well as social media.&lt;/p&gt;

&lt;p&gt;Looking for help or experienced translators? We created a club in &lt;a href="https://hub.shopware.com/clubs/translators" rel="noopener noreferrer"&gt;hub.shopware.com&lt;/a&gt;, especially for translators. We will be there to answer your translator questions.&lt;/p&gt;

&lt;p&gt;Have fun with this new feature and let us know how it is working for you!&lt;/p&gt;

</description>
      <category>shopware</category>
      <category>localization</category>
      <category>languages</category>
      <category>crowdin</category>
    </item>
    <item>
      <title>How to update php-redis extension (and why you might need to do so during shopware updates)</title>
      <dc:creator>Jonas Elfering</dc:creator>
      <pubDate>Fri, 27 Feb 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/shopware/how-to-update-php-redis-extension-and-why-you-might-need-to-do-so-during-shopware-updates-9ma</link>
      <guid>https://dev.to/shopware/how-to-update-php-redis-extension-and-why-you-might-need-to-do-so-during-shopware-updates-9ma</guid>
      <description>&lt;p&gt;This post gives you an explanation on why you might need to update the php-redis when upgrading to the latest shopware versions and how you can do so hassle-free&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you might need to update the &lt;code&gt;php-redis&lt;/code&gt; extension during shopware updates?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/shopware/shopware/blob/trunk/RELEASE_INFO-6.7.md#symfony-74-update" rel="noopener noreferrer"&gt;latest shopware 6.7.7.0 version&lt;/a&gt; included the upgrade to Symfony LTS version 7.4. Because the previously used Symfony 7.3 version &lt;a href="https://symfony.com/releases#symfony-releases-calendar" rel="noopener noreferrer"&gt;stopped being actively supported in Jan 2026&lt;/a&gt;, shopwares 6.6 LTS version will receive the Symfony update as well as part of the next release. &lt;br&gt;
The update from Symfony 7.3 to 7.4 is only a minor upgrade, so it should not include any breaking changes. While that is true on the code level of Symfony itself, Symfony &lt;a href="https://github.com/symfony/symfony/blob/7.4/UPGRADE-7.4.md#cache" rel="noopener noreferrer"&gt;did increase the needed minimum version&lt;/a&gt; of the &lt;code&gt;php-redis&lt;/code&gt; extension to 6.1 as part of the minor update. So if you currently use an older version of the extension, you will need to update the extension in order to successfully update shopware. This might especially be the case if you rely on older versions of Linux distributions; the 24.04 LTS version of Ubuntu, for example, ships &lt;code&gt;php-redis&lt;/code&gt; extension in version 5.3, which is not compatible, and you need to take action to update the package accordingly. &lt;/p&gt;
&lt;h2&gt;
  
  
  How can I figure out which &lt;code&gt;php-redis&lt;/code&gt; version I'm currently running?
&lt;/h2&gt;

&lt;p&gt;You can check the installed version of &lt;code&gt;php-redis&lt;/code&gt; extension by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;--re&lt;/span&gt; redis | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When that command indicates that you are running on version 6.1 or newer, you are ready to update to the latest shopware version without further action. If you're currently running an older version, you need to update it first.&lt;/p&gt;

&lt;p&gt;Additionally, Symfony added a composer conflict with older &lt;code&gt;php-redis&lt;/code&gt; versions, which means that the shopware upgrade (with the included Symfony upgrade) will throw a composer error if you still use an older version. &lt;/p&gt;

&lt;h2&gt;
  
  
  Should I update my &lt;code&gt;php-redis&lt;/code&gt; extension now?
&lt;/h2&gt;

&lt;p&gt;Generally, you should only look at upgrading &lt;code&gt;php-redis&lt;/code&gt; when you plan to upgrade to the latest shopware versions, which include the upgrade to Symfony 7.4. Upgrading the &lt;code&gt;php-redis&lt;/code&gt; version in existing, running applications provides no real benefit. And quite to the contrary, older shopware versions (especially when you are still on shopware 6.5) might not work with the latest &lt;code&gt;php-redis&lt;/code&gt; version. Therefore, you shouldn't upgrade just in case, only if you are affected and want to upgrade to the latest shopware versions that require this. This guide shouldn't be considered a general best practice, but rather a troubleshooting guide for when you're upgrading and run into the need to update the &lt;code&gt;php-redis&lt;/code&gt; extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to update the &lt;code&gt;php-redis&lt;/code&gt; extension in Docker
&lt;/h2&gt;

&lt;p&gt;Docker makes it easy to control the full environment; therefore, it is straightforward to update the &lt;code&gt;php-redis&lt;/code&gt; extension in your Dockerfile.&lt;/p&gt;

&lt;p&gt;First you should download the &lt;a href="https://github.com/mlocati/docker-php-extension-installer" rel="noopener noreferrer"&gt;php extension installer for Docker&lt;/a&gt; and make it executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/install-php-extensions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That script helps you to manage php extensions. &lt;/p&gt;

&lt;p&gt;Next you can install the &lt;code&gt;php-redis&lt;/code&gt; extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;install-php-extensions redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, that will install the latest stable version of the extension. It might be the case that you pinned the installed version, and you need to upgrade that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-RUN install-php-extensions redis-6.0
&lt;/span&gt;&lt;span class="gi"&gt;+RUN install-php-extensions redis-6.3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you use the &lt;a href="https://github.com/shopware/docker/pkgs/container/docker-base" rel="noopener noreferrer"&gt;official shopware/docker image&lt;/a&gt;, that update is already done automatically, and you just need to make sure that you update to the latest version of the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to update the &lt;code&gt;php-redis&lt;/code&gt; extension in Ubuntu
&lt;/h2&gt;

&lt;p&gt;You can install the &lt;code&gt;php-redis&lt;/code&gt; extension in Ubuntu through the default package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; php-redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, depending on the Ubuntu version you are using, the extension available in the official package repository might be too old.&lt;/p&gt;

&lt;p&gt;In that case, you can install the package from the &lt;code&gt;ondrej/php&lt;/code&gt; package repository, which is the trusted source for pre-built php packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository ppa:ondrej/php &lt;span class="c"&gt;# Press enter when prompted.&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that when you install the &lt;code&gt;php-redis&lt;/code&gt; extension it should pick up the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; php-redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to install the extension locally (with PIE)
&lt;/h2&gt;

&lt;p&gt;There is a new extension installer in the PHP ecosystem called &lt;a href="https://www.php.net/manual/en/install.pie.intro.php" rel="noopener noreferrer"&gt;PIE&lt;/a&gt;. PIE makes installing extensions easier and installs the latest versions from Packagist. PIE is intended to replace the old PECL installer and works similarly to Composer. &lt;/p&gt;

&lt;p&gt;To install PIE follow the &lt;a href="https://github.com/php/pie/blob/1.4.x/docs/usage.md#installing-pie" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt; from the official repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fL&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; /tmp/pie.phar https://github.com/php/pie/releases/latest/download/pie.phar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; gh attestation verify &lt;span class="nt"&gt;--owner&lt;/span&gt; php /tmp/pie.phar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo mv&lt;/span&gt; /tmp/pie.phar /usr/local/bin/pie &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/pie
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you have PIE installed, you can install the &lt;code&gt;php-redis&lt;/code&gt; extension by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pie &lt;span class="nb"&gt;install &lt;/span&gt;phpredis/phpredis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to install the extension locally (with PECL)
&lt;/h2&gt;

&lt;p&gt;PECL is the old-school extension installer in the PHP ecosystem; setting it up is not as straightforward as PIE, so it is not recommended anymore. However, if you have already set up PECL, you can continue using that.&lt;/p&gt;

&lt;p&gt;If you have PECL set up, you can install the latest extension by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pecl &lt;span class="nb"&gt;install &lt;/span&gt;redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>php</category>
      <category>shopware</category>
      <category>redis</category>
      <category>upgrade</category>
    </item>
    <item>
      <title>Automate Procurement in Shopware with EDI integrations</title>
      <dc:creator>Sander Mangel</dc:creator>
      <pubDate>Wed, 07 Aug 2024 13:24:37 +0000</pubDate>
      <link>https://dev.to/shopware/automate-procurement-in-shopware-with-edi-integrations-946</link>
      <guid>https://dev.to/shopware/automate-procurement-in-shopware-with-edi-integrations-946</guid>
      <description>&lt;p&gt;As a retailer or wholesaler, much of the daily operations revolves around data exchange with suppliers. Product stock and pricing is continuously updated, new product offerings need to be added, and purchase orders must be sent out to fulfill client orders or replenish stock. This can equate to a full-time job for several employees, if not entire departments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Electronic Data Interchange&lt;/em&gt; (EDI) integrations are a great solution to reduce manual effort in many of these processes. EDI helps automate communication with your suppliers, improving efficiency and reducing errors. This article will explain the benefits of EDI integrations for catalog management, stock updates, and purchase orders. We will discuss what EDI is, the different EDI formats, why the right solution partner is crucial, and how to use an Integration Platform as a Service (IPaaS) solution to integrate EDI into Shopware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Automating Integrations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Catalog Management&lt;/strong&gt;: With EDI, product catalogs are automatically updated. You don't need to manually enter new products or change details. This saves time and ensures accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stock Updates&lt;/strong&gt;: EDI keeps your stock levels accurate. Suppliers can send stock updates directly to your system, so your Shopware store always shows correct stock levels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purchase Orders&lt;/strong&gt;: Automating purchase orders with EDI means orders are sent to suppliers instantly. This speeds up the process and reduces the risk of errors in order details.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is EDI?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Electronic Data Interchange&lt;/em&gt; (EDI) is a technology that allows different businesses to exchange documents in a standardized electronic format. Instead of using paper, email, or fax, businesses can send documents like purchase orders and invoices directly from one computer system to another. EDI was first introduced in the 1960s and became more widely adopted in the 1980s and 1990s. This technology helps ensure that the data is accurate and sent quickly.&lt;br&gt;
For EDI to work, both the supplier and the retailer need to have EDI implemented. This is often initiated by the supplier. EDI is ideal for automating integrations because it standardizes the data exchange process. It allows different systems to communicate easily, reducing the need for manual work and minimizing mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding EDI Formats
&lt;/h2&gt;

&lt;p&gt;There are different EDI formats, and knowing their differences is important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;ANSI X12&lt;/em&gt;: Common in the United States, used for various business documents.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;EDIFACT&lt;/em&gt;: Widely used in Europe and internationally.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Proprietary Variants&lt;/em&gt;: Some companies and software providers, like  COMARCH, have their own EDI standards. These are tailored to specific business needs and may offer additional features.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Industry-Specific Variants&lt;/em&gt;: Certain industries have their own EDI standards. For example, the automotive industry uses standards like ODETTE, while the retail industry may use standards developed by GS1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each format has its strengths, and the choice depends on your specific needs and the region you operate in.&lt;br&gt;
While EDI is a standardized document format, it is by no means plug and play. Each party has their own specific values for specific fields, and ultimately a mapping between these values needs to be made. For example, while you might use a product property called "table top color" with a value such as "Dark Oak", a supplier might call that same property "surface color" with a property value of "Brown wood".&lt;/p&gt;

&lt;h2&gt;
  
  
  The Value of an EDI Implementation Partner
&lt;/h2&gt;

&lt;p&gt;Reaping the benefits of EDI is as much a technical matter as it is one of connecting to the right network. EDI implementation partners build networks of companies for which they already have existing integrations and, more importantly, value mappings. These companies might also have their own format standards, such as COMARCH.&lt;br&gt;
Most EDI implementation partners offer their own modern API layer, which simplifies the integration process. This API layer allows you to connect your Shopware system to the EDI network more easily and efficiently.&lt;br&gt;
The right partner will advise on which formats to support, can quickly integrate a large number of suppliers, and helps with creating the mapping to your specific data models. This means you can leverage their expertise and existing connections to make the integration process smoother and more efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating via EDI with Shopware
&lt;/h2&gt;

&lt;p&gt;Generally, integrations are made via an ERP, OMS, or IMS system, and less so directly with an ecommerce platform. Having said that, direct integration with Shopware can be a valid approach.&lt;br&gt;
We recommend leveraging an IPaaS solution to connect with the APIs of the EDI implementation partner. &lt;br&gt;
If only one supplier is integrated, it might be an option to directly ingest the EDI documents into the IPaaS solution, although this might lead to more maintenance work over time.&lt;br&gt;
From the IPaaS solution, the integration with Shopware is quickly made by finding the standard integration in the IPaaS solution's integration store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Leveraging EDI integrations with Shopware can greatly enhance your business operations. Automating catalog updates, stock management, and purchase orders improves efficiency and accuracy. Understanding different EDI formats helps you choose the right one for your needs. Finding a partner with a large network can simplify the process, or you can opt for an IPaaS solution to manage integrations yourself. Embracing EDI integrations will streamline your business and help you stay competitive in the market.&lt;/p&gt;

</description>
      <category>shopware</category>
      <category>automation</category>
    </item>
    <item>
      <title>Gross and Net-Switch for B2B and B2C Shops built with Composable Frontends</title>
      <dc:creator>Björn Meyer</dc:creator>
      <pubDate>Wed, 04 Oct 2023 10:11:49 +0000</pubDate>
      <link>https://dev.to/shopware/gross-and-net-switch-for-b2b-and-b2c-shops-built-with-composable-frontends-2b24</link>
      <guid>https://dev.to/shopware/gross-and-net-switch-for-b2b-and-b2c-shops-built-with-composable-frontends-2b24</guid>
      <description>&lt;p&gt;As a seller, you always want to offer your customers the best possible user experience. If an online store is not exclusively geared towards B2C or B2B, this becomes difficult, as these target groups have different requirements. One of the main requirements of B2B customers is, for example, the display of prices without VAT.&lt;/p&gt;

&lt;p&gt;Whether the prices are displayed with or without VAT is controlled in Shopware via the customer group. However, if the user is not logged in, he automatically receives the default customer group, which has set the price display incl. VAT in the default.&lt;/p&gt;

&lt;p&gt;In order to enable the user to switch from B2C to B2B in a non-logged-in state, a corresponding switch is offered in the store in many cases.&lt;/p&gt;

&lt;p&gt;For a Shopware installation with default storefront (Twig) the way seems very obvious, but for the implementation with Composable Frontends we asked ourselves some questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where should the setting of the switch be stored?&lt;/li&gt;
&lt;li&gt;How does the correct price output take place?&lt;/li&gt;
&lt;li&gt;How can a corporate customer be automatically registered with the B2B customer group?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Local storage instead of session
&lt;/h2&gt;

&lt;p&gt;If we would probably store the switch setting in the session (PHP) so far, our choice for Composable Frontends falls on the local storage. The reason for this is that the handling of the price display mainly takes place in the frontend and the adjustment is easier to implement here.&lt;/p&gt;

&lt;p&gt;For this purpose we use the &lt;code&gt;useLocalStorage()&lt;/code&gt; function of the &lt;a href="https://vueuse.org/core/useStorage/"&gt;VueUse&lt;/a&gt; Collection, which simplifies the handling of the variable from the local storage and brings the usual reactivity of Vue at the same time.&lt;/p&gt;

&lt;p&gt;So we need a Vue component for the switch, we call this component TaxSwitch. We include this in the header so that it is visible on every page in the store.&lt;/p&gt;

&lt;p&gt;The switch receives the values &lt;code&gt;net&lt;/code&gt; (for B2C) and &lt;code&gt;gross&lt;/code&gt; (for B2B). Since by default the customers are B2C customers, the local storage variable in the second parameter receives the default value &lt;code&gt;gross&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The button should only be visible when the customer is not logged in, which we achieve with the simple query &lt;code&gt;v-if="!isLoggedIn"&lt;/code&gt; within the component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yeggvc0J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5c0hymxggm904bejjzdu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yeggvc0J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5c0hymxggm904bejjzdu.png" alt="Shows a switch to change prices from net to gross" width="358" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 We use &lt;a href="https://vuetifyjs.com/en/components/all/"&gt;Vuetify&lt;/a&gt; components (v-switch, v-col) in the templates above, these are recognizable by the v- prefix. To use the same components you need to install it into your project via a package manager.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLocalStorage&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from "@vueuse/core";
const &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; = useUser();
const taxSwitch = useLocalStorage("taxSwitch", "gross");
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  // ... v-switch component is provided by vuetify
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;switch&lt;/span&gt;
    &lt;span class="na"&gt;v-if&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"!isLoggedIn"&lt;/span&gt;
    &lt;span class="na"&gt;v-model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"taxSwitch"&lt;/span&gt;
    &lt;span class="na"&gt;hide-details&lt;/span&gt;
    &lt;span class="na"&gt;true-value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"net"&lt;/span&gt;
    &lt;span class="na"&gt;false-value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"gross"&lt;/span&gt;
    &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-0-0"&lt;/span&gt;
    &lt;span class="na"&gt;data-testid&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tax-switch"&lt;/span&gt;
  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="na"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Private&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Company&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Price calculation based on the setting of the B2C / B2B switch
&lt;/h2&gt;

&lt;p&gt;In Shopware store API, gross and net prices are not returned at the same time, so we need to calculate the prices according to the switch setting.&lt;/p&gt;

&lt;p&gt;To simplify this, we use the &lt;a href="https://github.com/shopware/frontends/blob/main/templates/vue-demo-store/components/shared/SharedPrice.vue"&gt;SharedPrice component&lt;/a&gt; from the &lt;a href="https://frontends.shopware.com/getting-started/templates/demo-store-template.html"&gt;Demo Store template&lt;/a&gt; consistently for every price display in the store. This way we only have to implement the logic in one place.&lt;/p&gt;

&lt;p&gt;We have to calculate the price only in the following case:&lt;/p&gt;

&lt;p&gt;If the customer is &lt;strong&gt;not logged in&lt;/strong&gt;&lt;br&gt;
and the &lt;strong&gt;switch&lt;/strong&gt; has the &lt;strong&gt;value net&lt;/strong&gt;&lt;br&gt;
and the &lt;strong&gt;store setting&lt;/strong&gt; of the tax has the value &lt;strong&gt;gross&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In all other situations, the price is passed on without calculation.&lt;/p&gt;

&lt;p&gt;Depending on the implementation, there are places in the cart and checkout where no calculation of the tax should take place. That's why we decided to use the &lt;code&gt;props.taxRate&lt;/code&gt; as an indicator for this purpose. If this &lt;strong&gt;is&lt;/strong&gt; &lt;strong&gt;not&lt;/strong&gt; passed to the SharedPrice component, nothing has to be calculated.&lt;/p&gt;

&lt;p&gt;Furthermore, we have added an output to the price, which gives the user a hint (&lt;code&gt;taxHint&lt;/code&gt;) whether the price is displayed including or excluding VAT.&lt;/p&gt;

&lt;p&gt;Thanks to the reactive mechanics of the Vue framework, all prices in the frontend are automatically adjusted when the switch is changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
const &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getFormattedPrice&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; = usePrice();
const &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; = useUser();
const &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;taxState&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; = useSessionContext();
const taxSwitch = useLocalStorage("taxSwitch", "gross");
const props = defineProps&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;();
const getPrice = computed&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(() =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;taxSwitch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;net&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;taxState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gross&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;getFormattedPrice&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getFormattedPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;);
const taxHint = computed(() =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;taxSwitch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;net&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;taxState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;net&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ohne&amp;amp;nbsp;USt.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inkl.&amp;amp;nbsp;USt.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;);
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;slot&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"beforePrice"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;data-testid&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"shared-price"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPrice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-caption"&lt;/span&gt; &lt;span class="na"&gt;v-html&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"taxHint"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;slot&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"afterPrice"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatic registration of the customer group based on the setting of the B2C / B2B switch
&lt;/h2&gt;

&lt;p&gt;To ensure that a new B2B customer retains the correct display of VAT in the order process after registration, the B2B customer group must be set during registration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugin configuration
&lt;/h3&gt;

&lt;p&gt;For this reason, we create a plugin configuration with a selection field that allows you to specify the B2B customer group. Additionally, we added an option to activate the plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
  &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"https://raw.githubusercontent.com/shopware/platform/trunk/src/Core/System/SystemConfig/Schema/config.xsd"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;card&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;#ConVisTaxSwitch# Settings&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"de-DE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#ConVisTaxSwitch# Einstellungen&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input-field&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"bool"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Active&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"de-DE"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Aktiviert&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/input-field&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sw-entity-single-select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;b2bCustomerGroup&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;entity&amp;gt;&lt;/span&gt;customer_group&lt;span class="nt"&gt;&amp;lt;/entity&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Choose the B2B customer group.&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/component&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/card&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Customize registration form
&lt;/h3&gt;

&lt;p&gt;To allow the user to register as a company in the registration process, we have added the fields &lt;code&gt;AccountType&lt;/code&gt; and &lt;code&gt;Company&lt;/code&gt; to the registration form of the Demo Store template.&lt;/p&gt;

&lt;p&gt;In the initial state we automatically set the &lt;code&gt;AccountType&lt;/code&gt; to &lt;strong&gt;private&lt;/strong&gt;, if the switch is set to &lt;code&gt;gross&lt;/code&gt; we change it to &lt;strong&gt;business&lt;/strong&gt;. To make the &lt;code&gt;Company&lt;/code&gt; field mandatory for the &lt;code&gt;AccountType&lt;/code&gt; &lt;strong&gt;business&lt;/strong&gt;, we extend the form validation rules accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/checkout/index.vue&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
// ...
const state = reactive(&lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;accountType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taxSwitch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gross&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;business&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;salutationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;guest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;billingAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;zipcode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;countryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;countryStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;customShipping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;);
// ...
const rules = computed(() =&amp;gt; (&lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;accountType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;salutationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requiredIf&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;billingAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requiredIf&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;business&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;zipcode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;countryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;countryStateId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requiredIf&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;getStatesForCountry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;billingAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryId&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="si"&gt;}&lt;/span&gt;,
}));
// ...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// pages/checkout/index.vue&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  // ... v-col component is provided by vuetify
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;col&lt;/span&gt; &lt;span class="na"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;
      &lt;span class="na"&gt;v-model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"state.accountType"&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"accountType"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$t('form.accountType')"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"accountTypes"&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"accountType"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;
      &lt;span class="na"&gt;data-testid&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"registration-account-type-select"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;error-messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$v.accountType.$errors.map((e) =&amp;gt; e.$message)"&lt;/span&gt;
      &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$v.accountType.$touch()"&lt;/span&gt;
      &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;modelValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"changeTaxSwitchByAccountType"&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  // ... v-col component is provided by vuetify
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;col&lt;/span&gt; &lt;span class="na"&gt;v-if&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"state.accountType === 'business'"&lt;/span&gt; &lt;span class="na"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;text-field&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"company"&lt;/span&gt;
      &lt;span class="na"&gt;v-model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"state.billingAddress.company"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$t('form.company') + ' *'"&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"company"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
      &lt;span class="na"&gt;autocomplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"company"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;
      &lt;span class="na"&gt;data-testid&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"registration-company-input"&lt;/span&gt;
      &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;error-messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$v.billingAddress.company.$errors.map((e) =&amp;gt; e.$message)"&lt;/span&gt;
      &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"$v.billingAddress.company.$touch()"&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;v&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;col&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  // ...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Subscriber
&lt;/h3&gt;

&lt;p&gt;In order to be able to directly overwrite the customer data with the B2B customer group after registration, we create a subscriber for the &lt;code&gt;CustomerEvents::CUSTOMER_WRITTEN_EVENT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the plugin is active and the &lt;code&gt;Company&lt;/code&gt; field has a value, the user is a B2B customer according to our definition. Thus, we set the B2B customer group that we had previously defined in the plugin configuration for this customer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ConVis\TaxSwitch\Subscriber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Shopware\Core\Checkout\Customer\CustomerEvents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Shopware\Core\Framework\DataAbstractionLayer\EntityRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Shopware\Core\System\SystemConfig\SystemConfigService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\EventDispatcher\EventSubscriberInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateCustomerSubscriber&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;EventSubscriberInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;EntityRepository&lt;/span&gt; &lt;span class="nv"&gt;$customerRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;SystemConfigService&lt;/span&gt; &lt;span class="nv"&gt;$systemConfigService&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getSubscribedEvents&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;CustomerEvents&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CUSTOMER_WRITTEN_EVENT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'onCustomerWritten'&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onCustomerWritten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;EntityWrittenEvent&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$salesChannelId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getSource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getSalesChannelId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$pluginActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;systemConfigService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ConVisTaxSwitch.config.active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$salesChannelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$pluginActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$b2bCustomerGroupId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;systemConfigService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ConVisTaxSwitch.config.b2bCustomerGroup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$salesChannelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$writeResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getWriteResults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$writeResults&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'company'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'groupId'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$b2bCustomerGroupId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customerRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s1"&gt;'groupId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b2bCustomerGroupId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'requestedGroupId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the customer has set the switch to B2B, now in the store not only the price display is adjusted, but also automatically receives the B2B customer group during registration and during checkout.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This blog post was provided by our partner &lt;a href="https://www.con-vis.de/"&gt;con-vis&lt;/a&gt; and René Altmann. It shows very well that the headless approach with Shopware Composable Frontends can be suitable for both B2C and B2B customers. Thank you for sharing this knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>nuxt</category>
      <category>shopware</category>
      <category>vue</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Mocks and their Influence on Software Design</title>
      <dc:creator>Jonas Elfering</dc:creator>
      <pubDate>Fri, 08 Sep 2023 09:05:00 +0000</pubDate>
      <link>https://dev.to/shopware/mocks-and-their-influence-on-software-design-3o49</link>
      <guid>https://dev.to/shopware/mocks-and-their-influence-on-software-design-3o49</guid>
      <description>&lt;p&gt;When speaking about unit testing, one automatically also speaks about mocks and the need to mock away dependencies.&lt;/p&gt;

&lt;p&gt;It seems to be quite a typical attitude towards mocks along the lines of "Mock every external dependency of the class under test" and this attitude can be quite dangerous.&lt;/p&gt;

&lt;p&gt;Therefore, here are some words of caution on the implication that the heavy use of mocks in your code base can have in terms of the overall system design or architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Mocks are used to provide isolation in unit tests, and thus should help to create software that is &lt;strong&gt;easy to test&lt;/strong&gt; and &lt;strong&gt;easy to change&lt;/strong&gt;. But in fact, an over-usage of mocks in any unit test suite can cover up underlying issues in the software design that make the software &lt;strong&gt;hard to test&lt;/strong&gt; and &lt;strong&gt;hard to change&lt;/strong&gt;. So Mocks should be treated as one tool in your toolbox that one can use to build high-quality and maintainable software. That means one should know when to apply this specific tool, or when should rather use another tool from the toolbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocks are hard to refactor
&lt;/h2&gt;

&lt;p&gt;Be cautious when utilizing mocks extensively because it can be hard to automatically refactor classes. This is because IDEs do not provide robust support for refactoring classes that are heavily mocked, and tools like &lt;a href="https://phpstan.org/"&gt;PHPStan&lt;/a&gt; may not effectively detect these mock-related issues.&lt;/p&gt;

&lt;p&gt;More broadly speaking, it is hard to guarantee that the mock behaves in the same/or intended manner as the real implementation (especially when the underlying implementation changes).&lt;/p&gt;

&lt;p&gt;Use mocks only where you need to because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating the objects is hard as you need tons of nested dependencies to create the object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the class produces some side effects that you don't want in unit tests (e.g., DB writes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all other cases, use real implementations and rely as minimally as possible on the magic of PHPUnit's mocking framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on behavior, not implementation: Effective unit testing principles
&lt;/h3&gt;

&lt;p&gt;Relying heavily on mocks creates a bad pattern in unit tests of testing &lt;strong&gt;how&lt;/strong&gt; something is implemented and not &lt;strong&gt;what&lt;/strong&gt; the implementation actually does. If tests are implemented in a mock-heavy way, they are tightly coupled to the implementation, meaning they rely on implementation details and may fail more often when the implementation details change than when the actual behavior of the class under test changes. Consider these two example changes to some classes:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;searchIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;firstId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fetchAllAssociative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SELECT first, second FROM foo ...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mapToKeyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fetchKeyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SELECT first, second FROM foo ...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By definition, both changes are a pure example of refactoring:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.&lt;br&gt;
-&amp;gt; &lt;a href="https://refactoring.com/"&gt;Martin Fowler&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But when the unit test mocked the &lt;code&gt;repository&lt;/code&gt; or &lt;code&gt;connection&lt;/code&gt; dependencies, the unit tests will fail after the change, even though the external behaviour (that's what a test should really test) was not changed.&lt;/p&gt;

&lt;p&gt;Using mocks is ok in some cases, but not all.&lt;/p&gt;

&lt;p&gt;Probably the examples from above are ones that are totally valid (as the mocked classes rely on a DB), which can be commonly encountered in real life.&lt;/p&gt;

&lt;p&gt;Furthermore, the intention of this document is to keep you aware of the downsides that come with using mocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocks might indicate your class is not well-designed
&lt;/h2&gt;

&lt;p&gt;In a well-designed and testable system, it is relatively easy to isolate individual classes or modules and distinguish them between the components that contain the core business logic, which should be extensively unit tested, and the portions responsible for interfacing with the external environment and generating side effects. These side-effect-prone elements should be substituted in the unit tests. In fact, it is advisable to perform integration testing since their primary purpose is to abstract and facilitate the replacement of side effects in tests.&lt;/p&gt;

&lt;p&gt;This kind of abstraction follows when you apply the principles of &lt;a href="https://martinfowler.com/bliki/DomainDrivenDesign.html"&gt;Domain Driven Design&lt;/a&gt; and &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/"&gt;Hexagonal Architecture&lt;/a&gt; (aka Ports &amp;amp; Adapters). I've written in more detail about those architectural considerations in my &lt;a href="https://dev.to/shopware/benefits-of-separating-core-code-from-infrastructure-code-302j"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The absence of such abstraction in an existing codebase is one of the reasons why it can be quite hard to write "good" unit tests.&lt;/p&gt;

&lt;p&gt;So, a heavy reliance on mocks when writing unit tests can indicate a potential issue with the software design, suggesting insufficient encapsulation. Hence, designing code to promote better encapsulation and reduce the need for extensive mocking is advisable. This can lead to improved testability and overall software quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better options than mocks
&lt;/h2&gt;

&lt;p&gt;There are better options, but that depends on the use cases. Here are a few alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use the real implementation (this means the real dependency is easy to create and does not produce side effects)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a hand-crafted dummy implementation of the real dependency, that is easy to configure and behaves like a stub in that use case (this means that the real dependency probably needs to be designed in a way to be easy to replace)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fallback to using a mocking framework (when the real dependency is not designed to be replaced easily)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The way you design your codebase directly impacts whether you can rely on option 1 or option 2 without resorting to heavy mocking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Write tests first!
&lt;/h2&gt;

&lt;p&gt;When you write tests first, most of the points described above should come out of the box!&lt;/p&gt;

&lt;p&gt;Nobody who starts with a test would start with configuring a mock.&lt;/p&gt;

&lt;p&gt;While we provide insights on this, it is essential to validate the information. So we encourage you to explore the following references to gain a deeper understanding and form your own opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Frank De Jonge on the exact same topic (with more examples in PHP): &lt;a href="https://blog.frankdejonge.nl/testing-without-mocking-frameworks/"&gt;https://blog.frankdejonge.nl/testing-without-mocking-frameworks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Martin Fowler on the differences between mocks (option 3) and stubs (option 2): &lt;a href="https://martinfowler.com/articles/mocksArentStubs.html"&gt;https://martinfowler.com/articles/mocksArentStubs.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Presentation by Mathias Noback on testing hexagonal architectures: &lt;a href="https://matthiasnoback.nl/talk/a-testing-strategy-for-hexagonal-applications/"&gt;https://matthiasnoback.nl/talk/a-testing-strategy-for-hexagonal-applications/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some good real-life examples on unit tests in php: &lt;a href="https://github.com/sarven/unit-testing-tips"&gt;https://github.com/sarven/unit-testing-tips&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A great write-up on testing in general: &lt;a href="https://dannorth.net/2021/07/26/we-need-to-talk-about-testing/"&gt;https://dannorth.net/2021/07/26/we-need-to-talk-about-testing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quite an old (1997!) paper on how &lt;strong&gt;not&lt;/strong&gt; to use code coverage: &lt;a href="http://www.exampler.com/testing-com/writings/coverage.pdf"&gt;http://www.exampler.com/testing-com/writings/coverage.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great blog post series on how to avoid mocks: &lt;a href="https://philippe.bourgau.net/categories/#how-to-avoid-mocks-series"&gt;https://philippe.bourgau.net/categories/#how-to-avoid-mocks-series&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>architecture</category>
      <category>testing</category>
    </item>
    <item>
      <title>Benefits of separating core code from infrastructure code</title>
      <dc:creator>Jonas Elfering</dc:creator>
      <pubDate>Thu, 31 Aug 2023 12:54:00 +0000</pubDate>
      <link>https://dev.to/shopware/benefits-of-separating-core-code-from-infrastructure-code-302j</link>
      <guid>https://dev.to/shopware/benefits-of-separating-core-code-from-infrastructure-code-302j</guid>
      <description>&lt;p&gt;When it comes to software design and software architecture, there are many theoretical approaches with fancy acronyms (like &lt;a href="https://en.wikipedia.org/wiki/SOLID"&gt;SOLID&lt;/a&gt;, &lt;a href="https://dannorth.net/2022/02/10/cupid-for-joyful-coding/"&gt;CUPID&lt;/a&gt;, &lt;a href="https://martinfowler.com/bliki/DomainDrivenDesign.html"&gt;DDD&lt;/a&gt;, &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/"&gt;hexagonal architecture&lt;/a&gt;, &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html"&gt;clean architecture&lt;/a&gt;, …). However, it is not always easy to translate these concepts into the practical world, and it is not clear where they overlap or not. Therefore, it's important to think about the practices all academic design patterns have in common and start from &lt;a href="https://fs.blog/first-principles/"&gt;first principles&lt;/a&gt;. One really concrete practice that is encouraged by almost all software design flavors is the separation of core and infrastructure code. This post will explore many of the benefits and challenges through a real-life, practical example, so that by the end you will have a better understanding of how and why any software system might implement this tenant of sustainable architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is core and what is infrastructure code anyways?
&lt;/h2&gt;

&lt;p&gt;Every software system consists of core and infrastructure code. Infrastructure code, in simple terms, is the code that connects the main business logic (core code) to the outside world (e.g. to the web server or database). For a definition of core code, we can refer to &lt;a href="https://matthiasnoback.nl/"&gt;Matthias Noback's&lt;/a&gt; book “&lt;a href="https://matthiasnoback.nl/book/advanced-web-application-architecture/"&gt;Advanced Web Application Architecture&lt;/a&gt;”:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rule 1:&lt;br&gt;
Core code doesn’t directly depend on external systems, nor does it depend on code written for interacting with a specific type of external system.&lt;br&gt;
Rule 2:&lt;br&gt;
Core code doesn’t need a specific environment to run in, nor does it have dependencies that are designed to run in a specific context only.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A common symptom of weak software design is if, at the code level, it is difficult to distinguish between the two. This lack of separation reflects a web of dependencies that create a host of issues, all of which we will gradually walk through with the example, below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the purpose of this blog post, Infrastructure code should not be confused with “Infrastructure as Code” (the code needed to spin up the server/cloud infrastructure that the application runs on).&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;Consider the following &lt;a href="https://symfony.com/"&gt;Symfony&lt;/a&gt; controller used to post a new review by a customer for a given product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;#[Route(path: '/store-api/product/{productId}/review', methods: ['POST']]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;SalesChannelContext&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;NoContentResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var CustomerEntity $customer */&lt;/span&gt;
    &lt;span class="nv"&gt;$customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFirstName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nv"&gt;$review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'productId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'customerId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'externalUser'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'points'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$review&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$review&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventDispatcher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReviewFormEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NoContentResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is rather straight forward to read: the controller accepts some data, fills in some fall-back data based on the customer currently logged-in, validates the input, saves the review to the database and dispatches a domain event. In a nutshell, this controller does too much at once. Through the course of this post, we will use this example to study the problems this can cause, and gradually refactor it to illustrate how separating the core code from infrastructure code inherently minimizes these issues or resolves them entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evolve infrastructure and core independently
&lt;/h2&gt;

&lt;p&gt;Over time, your understanding of the underlying domain you are working in will improve, and you naturally will update your business logic to reflect this learning. In the best case, you want to have a single place where this knowledge is stored. On the other hand, if you want to grow your business, there may be other use cases where you want to provide the same business functionality (posting reviews) in different contexts. So there is a need for the infrastructure code and the core code to be able to evolve independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reusability
&lt;/h3&gt;

&lt;p&gt;In the controller example above, one can easily imagine that a new business requirement may arise that a merchant should be able to bulk import reviews from an existing review system. In the current implementation, this is not easily possible, because all the code is in a single place and it is not really reusable. In this scenario, the same business rules should apply, and the same domain events should be dispatched, because for the core domain logic, it is probably not that important from which source a new review was posted. Instead of copy &amp;amp; pasting a bunch of code to make the new use case work, the better option would be to separate the domain logic that is responsible for posting a review from the controller that provides that functionality over an HTTP-API. &lt;/p&gt;

&lt;p&gt;So our code may now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;#[Route(path: '/store-api/product/{productId}/review', methods: ['POST'])]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;SalesChannelContext&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;NoContentResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var CustomerEntity $customer */&lt;/span&gt;
    &lt;span class="nv"&gt;$customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFirstName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;postProductReviewService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NoContentResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostProductReviewService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'productId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'customerId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'externalUser'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'points'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$review&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$review&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventDispatcher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReviewFormEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that structure in place, it is now rather easy to implement the “Bulk Import Reviews” functionality, by reusing the extracted service. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The new core service still expects a &lt;code&gt;RequestDataBag&lt;/code&gt; parameter, which is not optimal but will be tackled further down.&lt;/p&gt;

&lt;p&gt;This refactoring step also greatly helps in cases when there comes a requirement for additional validation, that a customer actually has purchased the product in the past before the review is saved. After the refactoring, there is a single place where that logic can be added and we can evolve the core code independently from the infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching the underlying technology
&lt;/h3&gt;

&lt;p&gt;Another case where infrastructure code may change but the core code should not be affected is when there is the need to switch the underlying technology. There is much talk about being able to (theoretically) switch the whole data storage layer for the complete application without affecting the core code. I have to admit that I have never seen that work out in real life, and I think that these kinds of replacements should not be our end goal, as such an application probably would be over-engineered. But the possibility to switch out underlying technology for specific parts of the application without having to touch the rest of the system can be a sweet spot. &lt;/p&gt;

&lt;p&gt;Back to our code example, perhaps some requirement arises where product reviews should not be saved in the relational database, but rather posted to an external review system that should be integrated. To make this change easier, the code that actually takes care of saving the data should be extracted from the core service (as that is actually infrastructure code) and an interface should be introduced, so that the core service only relies on the interface. &lt;/p&gt;

&lt;p&gt;The code could be refactored like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostProductReviewService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productReviewGateway&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;postReviewByCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventDispatcher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReviewFormEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewGateway&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;postReviewByCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;RequestData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DalProductReviewDataGateway&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewGateway&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;postReviewByCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ProductReviewPostData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ProductReviewStatus&lt;/span&gt; &lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'productId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'customerId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'externalUser'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'points'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$review&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$review&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fact that we use the &lt;a href="https://developer.shopware.com/docs/concepts/framework/data-abstraction-layer"&gt;Shopware DAL&lt;/a&gt; to store the reviews is now an implementation detail. We can easily change that storage layer without having to touch the core business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveat: Finding the right abstractions
&lt;/h3&gt;

&lt;p&gt;When creating abstractions to hide technical decisions from the consumer of that code, a major challenge is defining the right level of abstraction. &lt;/p&gt;

&lt;p&gt;On the one hand, it is easy to create an “abstraction” that is too specific and tightly coupled to the implementation by &lt;a href="https://en.wikipedia.org/wiki/Leaky_abstraction"&gt;leaking technical details as part of the interface&lt;/a&gt;. This leads to a situation where even if there is an abstraction in place, it is not possible to switch the actual implementation without breaking the abstraction.&lt;/p&gt;

&lt;p&gt;On the other hand, it might also happen that abstractions are too generic. If that is the case, then clients of the abstraction have to implement solutions for common cases that are actually better solved by the implementation of that abstraction.&lt;/p&gt;

&lt;p&gt;So finding the right level of abstraction is always a trade-off between either too specific or too general.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testability
&lt;/h2&gt;

&lt;p&gt;Being able to easily write unit tests for your mission critical business logic is a prerequisite in order to deliver high-quality software. If the infrastructure and the core code are not separated, it is hard to write these unit tests, as basically the whole system has to be in place in order to perform them. Looking back at the original single controller example from the beginning, in order to write a test for that piece of code, a database needs to be set up, the Shopware DAL needs to be functional, which means that the Symfony container must be set up properly, and you have to fake a request in order for the tests to run at all.&lt;/p&gt;

&lt;p&gt;We can already see that the refactoring we made in order to evolve the infrastructure separately from the core code already improves testability a lot, as we can now test the infrastructure code and the core code separately. The core code is especially easier to test, as we don’t need to have a running database and the Symfony container set up anymore; instead, we can use a Mock or dummy implementation of our &lt;code&gt;ProductReviewGateway&lt;/code&gt; interface. The only thing that makes unit testing of our core code (the &lt;code&gt;PostProductReviewService&lt;/code&gt;) harder than it needs to be is the dependency on an HTTP-Request in the form of the &lt;code&gt;RequestDataBag&lt;/code&gt; argument. In order to make it easier to test, we can define a &lt;a href="https://martinfowler.com/eaaCatalog/dataTransferObject.html"&gt;DTO&lt;/a&gt; that encapsulates the actual parameters that our service needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewPostData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromRequestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This DTO object can in itself be easily unit tested, and the adjusted service is now also easier to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostProductReviewService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ProductReviewPostData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productReviewGateway&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;postReviewByCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventDispatcher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReviewFormEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProductId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomerId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not only is the service now easier to test, but it is also easier to consume the service API as the expected input parameters are now well defined in the DTO, and there is no need anymore to guess the parameters and their names based on the implementation. Additionally, the type system gives us some simple validation for free, like validating required and optional parameters as well as their basic types.&lt;/p&gt;

&lt;p&gt;The controller now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;#[Route(path: '/store-api/product/{productId}/review', methods: ['POST'])]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;SalesChannelContext&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;NoContentResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var CustomerEntity $customer */&lt;/span&gt;
    &lt;span class="nv"&gt;$customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFirstName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$postData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewPostData&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromRequestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;postProductReviewService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$postData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NoContentResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The influence on complexity
&lt;/h2&gt;

&lt;p&gt;In order to keep a system in a state where it can be easily changed and evolved based on current needs, it is essential to keep the complexity of the overall system low. In general, the more things the system does, the more complex it becomes, but separating core from infrastructure code will help to keep complexity minimal. To see how many different things our original code does, we can separate it into the following “concerns”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How are the HTTP-Request parameters transformed so our application can handle them? (Infrastructure concern)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What business rules apply when a product review is posted? (Core concern)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How is a product review stored in a persistent storage? (Infrastructure concern)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our initial code handled all three concerns at the same time. That means when you want to understand how one of those concerns is solved, the code related to the other concerns is in fact adding complexity, which makes the part of the code you are actually interested in harder to understand. By separating out the concerns and handling them separately, it is easier to focus on one of the concerns without having to care how the others are solved; the level of complexity is reduced to a minimum. &lt;/p&gt;

&lt;p&gt;We can even improve our code a little bit further by moving the fallback handling (which gets the name from the currently logged-in customer) to where the rest of the DTO is created to have that in a single place as well. So the code might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;#[Route(path: '/store-api/product/{productId}/review', methods: ['POST'])]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;SalesChannelContext&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;NoContentResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var CustomerEntity $customer */&lt;/span&gt;
    &lt;span class="nv"&gt;$customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$postData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewPostData&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromRequestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;postProductReviewService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$postData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NoContentResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewPostData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromRequestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;CustomerEntity&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFirstName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'lastName'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that refactoring, it’s now obvious we added a &lt;code&gt;lastName&lt;/code&gt; field to the request based on the logged-in user, in case it was not provided by the client. But we actually never used &lt;code&gt;lastName&lt;/code&gt; anywhere when storing the review; the product review does not distinguish between first and last names, and has only one name field. So in the original code, there lay a bug in plain sight, but it was hidden by all the accidental complexity surrounding it. By separating the different concerns, the bug becomes obvious and easy to fix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewPostData&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromRequestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;CustomerEntity&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;RequestDataBag&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFirstName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getLastName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Improvements in collaboration
&lt;/h2&gt;

&lt;p&gt;Another big benefit of separating core code from infrastructure code is that it can help you improve the collaboration with the non-engineers in your team, be it QA, designers or the people from the business side. When you separate the infrastructure-related code from the core code, it is way easier to see how the business rules for your application are implemented and if they match what the domain experts would expect. In the example case, some of the most interesting parts of the code are quite hidden in the &lt;code&gt;$this-&amp;gt;validate($data, $context)&lt;/code&gt; call. That method currently looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ProductReviewPostData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$definition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DataValidationDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product.create_rating'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotBlank&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotBlank&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'min'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LessThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nv"&gt;$criteria&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Criteria&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EqualsFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EqualsFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// ensure that the customer can only post one review per product&lt;/span&gt;
    &lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EntityNotExists&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'entity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'product_review'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'context'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'criteria'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]));&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code uses the &lt;a href="https://symfony.com/doc/current/validation.html"&gt;Symfony validator&lt;/a&gt; component to perform the validation. The first part of the validation is rather straightforward: it performs some basic consistency checks on the data, e.g. that the name is filled, that the content of the review is at least 40 characters long and that the rating points are between one and five. With the new code structure, it would probably be best to move this part of the validation to the DTO object itself, so that it is not even possible to create a DTO that is invalid. One can still use the Symfony validator or perform those checks manually in the constructors. &lt;/p&gt;

&lt;p&gt;But the more interesting part is the last part of the code. There it uses some Shopware DAL magic to validate that the customer has not already reviewed the same product. This is a core business rule that should be enforced, but for now it is quite hidden in the validation code. To bring it into plain sight, we want to move this check into the body of the service, as that method should read like a workflow of what happens when a review is posted. To do so, we first create a new Interface for this kind of check, as only the Infrastructure layer can provide the information we need in the service, but those two layers should be kept separate. The interface might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProductReviewConstraints&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;hasCustomerAlreadyReviewedProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the implementation using the Shopware DAL might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DALProductReviewConstraints&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;hasCustomerAlreadyReviewedProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$criteria&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Criteria&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EqualsFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EqualsFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customerId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productReviewRepository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;searchIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$criteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;firstId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business rule that says a customer can only post a review for a product once is now more obvious in the domain flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostProductReviewService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ProductReviewPostData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;reviewConstraints&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasCustomerAlreadyReviewedProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProductId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomerId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nv"&gt;$context&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomerAlreadyReviewedProductException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomerId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProductId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productReviewGateway&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;postReviewByCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;eventDispatcher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReviewFormEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getProductId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCustomerId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a structure like that, it is easy to discuss with other peers if we actually implemented the requirements the right way or if there is something missing. It is easy to spot that there is no check in place whether the customer actually bought that product or not, so it can spark discussions about whether and how to add such an additional check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Keep infrastructure code at the boundaries of your codebase
&lt;/h2&gt;

&lt;p&gt;The code as we left it at the end of this post is by no means perfect, but it is a perfect starting point for further refactoring based on the advanced architecture patterns mentioned in the beginning (&lt;a href="https://martinfowler.com/bliki/DomainDrivenDesign.html"&gt;DDD&lt;/a&gt;, &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/"&gt;hexagonal architecture&lt;/a&gt;, &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html"&gt;clean architecture&lt;/a&gt;, …). For example, proponents of DDD probably would refactor the service in a way to encapsulate the business logic inside a rich domain model and not let it stay directly in the service itself. But especially in DDD, you also don’t want any outside dependencies in your domain model, so it’s a necessary step to first separate your core code from the infrastructure code.&lt;/p&gt;

&lt;p&gt;By now, this example walkthrough should already explain a lot of the benefits of separating the two. It makes it easier to interact with and evolve the codebase for you and your peers even when the requirements or the technology might considerably change. Ultimately, this is a general pattern that should be applied regardless of the architecture you have in place in your application, as it lays a concrete foundation for quality, sustainability and, dare I suggest, pleasure to work within your codebase.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>webdev</category>
      <category>php</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
