QUELLE: https://hub.shopware.com/learn/unit/debug-your-code-within-shopware

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
Great, we are making progress in this learning course. You have learned how to create a plugin, how to add custom data to a product, and how to create a custom controller.
But what if some parts in your code don’t work as expected? How can you find the root cause of the issue? This is where debugging comes into play.
In this learning unit, we will focus on how to debug your code within Shopware. You will learn how to use debugging tools, set breakpoints, inspect variables, trace the execution flow of your code, and use logging to identify issues. Additionally, we will cover common debugging techniques and best practices.
There are two different template sources. One being the Shopware repository that our community commits to and a production-optimized template that is a symfony flex recipe.
If you are using the Symfony Flex template:
composer create-project shopware/production <project-name>You will need to add the Symfony Webprofiler Bundle to your dev dependencies.
composer require --dev symfony/web-profiler-bundleDebugging should only be done in a safe environment, never in production.
Let’s check our .env file for the APP_ENV variable. It should be set to dev.
APP_ENV=devNow we refresh our homepage and should see a Symfony Profiler toolbar. If not, you might have to clear the cache with the following command:
bin/console cache:clearThe Symfony Profiler provides many helpful features and overviews, including a performance timeline, memory usage and detailed request insights. It can help you uncover critical issues in your codebase – for example, when rules are not active, or when there are too many cache tags.
Some of them are Symfony standard, like:
Others are Shopware specific:
Twig dump() is a function that can be used to dump variables in your templates.
{{ dump(variable) }}If you don’t pass a variable, it will dump all variables in the current context. This can be very helpful to see what data is available in your template and how it is structured.
Xdebug is a PHP extension that helps you debug your code. It provides a lot of valuable information such as stack traces, profiling, and code coverage.
Especially when working on complex data structures, setting breakpoints and inspecting variables can be very helpful.
Depending on your setup, you can install or activate Xdebug as follows:
If you are using the Shopware Devenv, you can simply enable Xdebug through the built-in configuration.
First, install the PHP extension with your package manager:
sudo apt update
sudo apt install php-xdebugThen configure your php.ini file:
sudo nano /etc/php/8.x/fpm/php.ini
zend_extension=xdebug.so
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=127.0.0.1
xdebug.client_port=9003After that, enable the extension:
sudo phpenmod xdebugFinally, restart your webserver:
sudo systemctl restart apache2If you are using Docker, follow this official Shopware guide to enable Xdebug.
Docker will be the recommended standard setup for Shopware in the future.
If you are using Dockware, it is recommended to use the #dev image, where you can simply enable it with a flag.
Dockware is planned to be deprecated in the future. It is recommended to migrate to a Docker-based setup for long-term compatibility.
If you are using PhpStorm, you can configure Xdebug in the IDE settings. Make sure to set the correct path mappings (important to set the public folder here), port, and server.
You can find a detailed guide in the official PhpStorm documentation.
You will find your logs in the var/log directory of your Shopware root directory. There are different log files for different purposes.
If your environment is set to dev, you will see a lot of logs in your dev.log file.
Shopware uses the monolog-bundle from Symfony, so you can configure your logging in the config/packages/dev/monolog.yaml file.
Find out more about logging in Shopware.
Finding performance bottlenecks can be a challenging task, especially in complex production setups.
Shopware provides a profiler interface that can help you identify performance issues.
The profilers differ in the way they are typically used.
Datadog and Tideways are two popular profiler backends that gather traces and performance data and have predefined dashboards.
OpenTelemetry is open-source and provides a lot of flexibility. You can use it to trace your code and send the data to different backends like Jaeger, Zipkin, or Prometheus.
We mentioned the Symfony Profiler already. It provides detailed information about the performance of your code but is only used locally in development environments.
The Frosh Development Helper is a Shopware plugin that provides a lot of useful tools for developers.
Some features are:
The Meteor Shopware 6 Toolkit is a Chrome extension that helps you jump to the right place in the Shopware 6 administration when being on a product detail page or category page.
Helpful when debugging or developing new features.
In this learning unit, you have learned:
dump function.With these tools and best practices, you can confidently trace, analyze, and fix issues within your Shopware plugins.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/add-data-to-your-product

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
The product is at the front and center of every Shopware shop. It is the most important entity and can be extended with custom data.
In this learning unit, we will take a look at ways in which we can programmatically add custom data to a product.
Before we start, we need to decide how we want to add custom data to our product. There are three main ways to do this:
Product properties: The easiest way to add custom data to a product. You can add properties like color, size, or any other custom property you need.
Custom fields: Custom fields are more flexible than product properties and can be used to store any kind of data.
Custom entities: If more complex data structures are required, you can create custom entities in Shopware. Custom entities are custom database tables, which can be used to store any kind of data.
| Feature | Product Properties | Custom Fields | Entity Extensions | 
|---|---|---|---|
| CRUD via Admin | Yes | Yes | Not by default | 
| Category filtering | Yes | No | No | 
| Variant generation | Yes | No | No | 
| Hide in Admin | No | Yes | Yes | 
| Migration needed | No | No | Yes | 
| Association possible | No | No | Yes | 
| Custom Entities | No | No | Yes | 
As shown, each method has its own advantages and disadvantages. Let’s break down the use cases:
Use product properties when you need to filter products in your category listing or generate variants based on the property.
Example: color: red, blue, green: Generates variants for each color. Needed for filtering and variant generation.
Use custom fields when you need to store additional, simple information that is not part of the standard attributes and does not require filtering or variant generation. They can be added to almost all entities (e.g., product entity, customer entity, order entity).
Example: dangerous-goods: true: Displays a warning on the product page if set to true. Not necessarily needed for filtering or variant generation.
In this learning unit, we will focus on adding custom fields to a product. Custom fields are a good compromise between flexibility and ease of use.
Use custom entities if you need to store complex data structures that are not related to the product itself.
Example: customize-product: COMPLEX DATABASE STRUCTURE: Stores complex data structures in separate database tables. For example a ring product configurator (material, size, engraving, etc.).
The Custom Products extension is a good example of the implementation of custom entities.
In our case, we want to add a custom field to our product that stores a “cargo” flag. If the flag is set to true, a warning should be displayed on the product page.
This information is not needed for filtering or variant generation, so we will use a custom field for this. It will also be used in the email notification for the warehouse.
To add a custom field to a product, we need to create a new custom fieldset and add a custom field to it. We can do this via the Shopware administration, or we can do it programmatically.
As of Shopware 6.7.0.0, custom field names and field set names must be valid Twig variable names. This means hyphens (-) and dots (.) are no longer allowed. Existing custom fields will continue to work. The validation is only enforced when creating new custom fields.
In our case, it makes sense to define custom fields programmatically because we want to automate the process of adding the custom field to our products. This plugin will be rolled out to all shops in the network, and to avoid human error on creation, we will automate the process.
If you feel exploratory, you can create a very basic plugin with the following command:
bin/console plugin:create AcademyDemoProductCustomFieldIn the interactive setup, you can choose the example custom fieldset option if you want Shopware to generate the basic file structure for you. In this learning unit, you will replace the generated example content before installing the plugin.
Do you want to create an example custom fieldset? (yes/no)The generated Shopware example custom fieldset uses generic technical names such as swag_example_set and swag_example_size. Custom field names must be unique in the database. If the generated example was installed before, installing another plugin with the same names can cause a duplicate-entry error.
Before you install the plugin, replace the generated example content with the code shown below. This learning unit uses its own custom field name, academy_demo_product_cargo. If you compare your code with the Academy reference plugin, remember that the reference plugin uses different names, such as academy_product_set and academy_product_cargo.
Keep this command for later. After you have added the code in this learning unit, install and activate the plugin with:
bin/console plugin:refresh
bin/console plugin:install AcademyDemoProductCustomField --activateOr you can use our AcademyProductCustomField reference plugin. The reference plugin follows the same pattern, but it uses the AcademyProductCustomField namespace and academy_* technical names.
The custom fields integration in this example is a combination of different parts:
| Part | Description | 
|---|---|
| The CustomFieldsInstaller | A PHP class that creates and removes the field set, relation, and custom field | 
| The plugin lifecycle | Calls the installer during install and uninstall | 
| The template | A Twig file that renders the custom field | 
In our case, we do not need to create a migration to add the custom field set, because we will use an entity (product) that already exists.
If you are more versed and have a custom entity, please take a look at the custom entity documentation.
The custom fieldset is a collection of custom fields that can be added to a product. In this example, the installer creates the field set, links it to the product entity, and adds the custom field in one nested write.
Replace the content of your generated CustomFieldsInstaller.php file with the following content:
<?php declare(strict_types=1);
namespace ProductDemoPlugin\Service;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\CustomField\CustomFieldTypes;
class CustomFieldsInstaller
{
    private const string CUSTOM_FIELD_SET_NAME = 'Academy Product Custom Field Set';
    private const string CUSTOM_FIELD_NAME = 'academy_demo_product_cargo';
    private const string CUSTOM_FIELD_TECHNICAL_NAME = 'Academy_Demo_Product_Cargo';
    public function __construct(
        private readonly EntityRepository $customFieldSetRepository
    ) {
    }
    public function install(Context $context): void
    {
        $this->customFieldSetRepository->upsert([
            [
                'id' => Uuid::fromStringToHex(self::CUSTOM_FIELD_SET_NAME),
                'name' => self::CUSTOM_FIELD_TECHNICAL_NAME,
                'position' => 0,
                'config' => [
                    'label' => [
                        'en-GB' => self::CUSTOM_FIELD_SET_NAME,
                        'de-DE' => self::CUSTOM_FIELD_SET_NAME,
                    ],
                ],
                'relations' => [
                    [
                        'id' => Uuid::fromStringToHex(self::CUSTOM_FIELD_SET_NAME . '_product_relation'),
                        'entityName' => ProductDefinition::ENTITY_NAME,
                    ],
                ],
                'customFields' => [
                    [
                        'id' => Uuid::fromStringToHex(self::CUSTOM_FIELD_NAME),
                        'name' => self::CUSTOM_FIELD_NAME,
                        'type' => CustomFieldTypes::BOOL,
                        'config' => [
                            'componentName' => 'mt-switch',
                            'type' => 'checkbox',
                            'label' => [
                                Defaults::LANGUAGE_SYSTEM => 'Cargo Flag',
                                'en-GB' => 'Cargo Flag',
                                'de-DE' => 'Cargo Flag',
                            ],
                        ],
                    ],
                ],
            ],
        ], $context);
    }
    public function uninstall(Context $context): void
    {
        $this->customFieldSetRepository->delete([
            [
                'id' => Uuid::fromStringToHex(self::CUSTOM_FIELD_SET_NAME),
            ],
        ], $context);
    }
}You do not need a separate services.xml registration for this example. The plugin lifecycle can create the installer and pass the custom_field_set.repository service directly.
Now we can call the installer from our plugin class:
<?php declare(strict_types=1);
namespace AcademyDemoProductCustomField;
use AcademyDemoProductCustomField\Service\CustomFieldsInstaller;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
class AcademyDemoProductCustomField extends Plugin
{
    public function install(InstallContext $installContext): void
    {
        parent::install($installContext);
        $this->installCustomFields($installContext->getContext());
    }
    public function uninstall(UninstallContext $uninstallContext): void
    {
        parent::uninstall($uninstallContext);
        if ($uninstallContext->keepUserData()) {
            return;
        }
        $this->uninstallCustomFields($uninstallContext->getContext());
    }
    private function installCustomFields(Context $context): void
    {
        $customFieldsInstaller = new CustomFieldsInstaller(
            $this->container->get('custom_field_set.repository')
        );
        $customFieldsInstaller->install($context);
    }
    private function uninstallCustomFields(Context $context): void
    {
        $customFieldsInstaller = new CustomFieldsInstaller(
            $this->container->get('custom_field_set.repository')
        );
        $customFieldsInstaller->uninstall($context);
    }
}Plugin lifecycle methods are commonly used to create or clean up plugin-owned setup data, such as custom field sets.
In this example, the plugin creates the custom field during installation and removes it during uninstallation only when the user does not keep plugin data.
Our class CustomFieldsInstaller
Uuid::fromStringToHex() to create stable IDs from these values.install() method creates or updates the custom field set, product relation, and academy_demo_product_cargo custom field in one nested write.uninstall() method removes the custom field set again when plugin data should be deleted.The plugin base class (AcademyDemoProductCustomField)
install() method calls installCustomFields(). bin/console plugin:install <PluginName>.uninstall() method calls uninstallCustomFields() only when the user does not keep plugin data. bin/console plugin:uninstall <PluginName>.Flow: When the plugin is installed, the custom field set is created, linked to the product entity, and filled with the academy_demo_product_cargo custom field. After that, the custom field is available in the product settings in the administration.
To display the custom field on the product detail page, we need to extend the buy-widget template and include our custom template.
In this example, we extend the original buy-widget and include a new Twig file that renders the custom field:
{% sw_extends '@Storefront/storefront/component/buy-widget/buy-widget.html.twig' %}
{% block buy_widget_ordernumber_container %}
    {{ parent() }}
    {% include '@AcademyDemoProductCustomField/storefront/component/academy-demo-product-cargo.html.twig' %}
{% endblock %}Here, we use sw_extends and parent to inherit the buy-widget template and extend it by using include to load our custom template.
In this learning unit, we use the standard Twig include syntax for simplicity. In advanced use cases, consider using Shopware’s sw_include tag, which supports multi inheritance.
It is always good practice to use includes in Twig. This way you can keep your code clean and modular.
Also, remember to wrap your custom field in a block, so you can override it in your theme.
{% block academy_demo_product_cargo %}
    {% if product.translated.customFields.academy_demo_product_cargo %}
        <div class="academy-demo-product-cargo">
            <img 
              src="{{ asset('bundles/academydemoproductcustomfield/images/cargo.png') }}"
              alt="Cargo shipping"
              style="width: 50px; height: 35px;"
            />
        </div>
    {% endif %}
{% endblock %}As some of you may be aware, you can add media to the custom field via CustomFieldTypes::MEDIA. Nonetheless, choosing to define our custom field in this way gives full control to the developer / plugin - a new release of our Plugin can contain a new image, and the rollout will change it in all shops. In contrast, the media field cannot be updated in this way.
So let’s add an image to the file system. This is done via the asset structure in Shopware.
# PluginRoot
├── composer.json
└── src
    ├── Resources
    │   └── public
    │       └── images
    │           └── cargo.png <-- Asset file here
    └── AcademyDemoProductCustomField.phpIf your image does not show up in the storefront, you can run the bin/console assets:install command to copy the assets to the public (PROJECT_ROOT/public/bundles) folder.
You can assign the custom field to a product via the Shopware administration. Go to the product detail page and click on the “Specifications” tab.
Here you can add the custom field to the product. In our case, we toggle the “Cargo flag” to true.
You can also assign the custom field to a product via the Admin API. You can use the POST /api/product/{productId} endpoint to update the product with the custom field.
PATCH http://YOUR_SHOP_URL/api/product/YOUR_PRODUCT_UUID
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKENWith the JSON body:
{
  "customFields": {
    "academy_demo_product_cargo": true
  }
}Learn more about the Admin API in our Stoplight documentation.
The result of our work is a product that has a custom field “Cargo flag” that can be toggled in the Shopware administration. If the flag is set to true, an image of a truck is displayed on the product page, indicating that our XXL washing machine is shipped via cargo.
If you did everything correctly but don’t see the cargo flag on the product page, run the following commands in your shop’s root directory:
bin/console cache:clear: Clears old cache and reindex compiled Twig templates.bin/console assets:install: Copies your plugin’s assets into the public bundles folder ([shop_root]/public/bundles). At this point, you may have already done it.bin/console theme:compile: Rebuilds your storefront theme so that the changes (theme configurations, assets, SCSS changes) become visible.In this learning unit, you have learned:
CustomFieldsInstaller and hook it into the plugin lifecycle (install, uninstall).With this, you have a solid foundation to extend products safely and consistently, from data modeling to storefront rendering and API updates.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/getting-started-with-shopware-backend-development

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
In this learning unit, you will implement a simple storefront controller and use it to display a modal window in the storefront.
Before we start implementing, you need to know where development begins. In Shopware, we have Plugins. A plugin is a server-side extension implemented as a Symfony bundle, like browser-addons for the browser.
 Plugins are the entry point of development without breaking the core logic of Shopware. You can implement controllers, services, event subscribers, database entities, CLI commands, scheduled tasks, and storefront templates and more.
 Classes from your plugin are registered in Symfony’s Dependency Injection (DI) container (via services.xml) and injected where needed.
To get a quick picture, check the screenshots below. They show you a minimal plugin structure, the entry points for controller and subscribers, and how services are registered with the DI container.
Feel free to dive deeper by exploring the official guide: Plugin base structure.
Every person is different and unique, so is every Shopware project. In many projects, however, you want to show something specific in a modal window to not interrupt the shopping experience.
This can be a newsletter subscription, a login form, or a product detail view. To achieve this, we will fetch some data from an external API and show it in a modal window.
We cover the topic in our official documentation on adding a custom controller.
We will walk through creating a storefront controller step by step, but we recommend reading the documentation first to get an overview.
If you feel like exploring, you can create a very basic plugin with the following command and in the interactive setup, choose the Storefront option to generate a controller that you can use in the storefront. For all other options, you can choose “no” to keep it clean and simple.
Your generated controller is called ExampleController and would need to be renamed if you want to follow this guide step by step.
bin/console plugin:create AcademyStorefrontController
 Do you want to create an example storefront controller? (yes/no) [yes]:The plugin:create command is a powerful command to create plugins with all necessary files and directories. Especially if you want to create entities, a JavaScript plugin, or an administration module.
Or you can use our example plugin, which already contains all necessary files.
# Clone the repository into your custom/plugins directory of your Shopware project
git clone git@github.com:ShopwareAcademy/AcademyStorefrontController.git custom/plugins/AcademyStorefrontControllerAfter creating or cloning the plugin, first refresh the plugin list so Shopware can detect the new plugin:
bin/console plugin:refreshThen install and activate the plugin:
bin/console plugin:install AcademyStorefrontController --activateOptionally, you can clear the cache to ensure your changes take effect:
bin/console cache:clearAlternatively, you can clear the cache automatically after the installation by using the --clearCache option:
bin/console plugin:install AcademyStorefrontController --activate --clearCachecomposer.json File (Optional)If you used the generator to create the plugin, it will have a composer.json file in the plugin directory. For your controller to work as expected, make sure your namespace autoloading is correctly configured. Add the following code snippet to the composer.json file of your generated plugin:
"autoload": {
    "psr-4": {
        "ShopwareAcademy\\StorefrontController\\": "src/"
    }
},
"autoload-dev": {
    "psr-4": {
        "ShopwareAcademy\\StorefrontController\\Tests\\": "tests/"
    }
}Then, in the root directory of your shop, rebuild the autoloader to make sure your changes take effect:
composer dump-autoloadYou can also check out our demo plugin, which already contains the correct autoload configuration.
If you used the generator to create the controller, it creates a controller called ExampleController. In this learning unit, we use ImageController. To rename it safely:
ImageController (e.g., src/Storefront/Controller/ImageController.php and class ImageController extends StorefrontController).ShopwareAcademy\StorefrontController\Storefront\Controller).services.xml is service id="ShopwareAcademy\StorefrontController\Storefront\Controller\ImageController" public="trueFinally, clear the cache so that your changes take effect by running the following command in your shop’s root directory:
bin/console cache:clearThe controller feature consists of four parts:
| Part | Description | 
|---|---|
| The controller | A PHP class | 
| The route import | Part of the routes.xmlfile | 
| The service definition | Part of the services.xmlfile | 
| The template | A Twig file | 
If you are a beginner, it is highly recommended to use the generator to create the controller. Often the little things like the correct namespace or the correct path can be tricky and lead to frustration.
First, you need to create the controller itself. This is a simple PHP class that extends the StorefrontController class.
<?php declare(strict_types=1);
namespace ShopwareAcademy\StorefrontController\Storefront\Controller;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\Response;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Route(defaults: [PlatformRequest::ATTRIBUTE_ROUTE_SCOPE => [StorefrontRouteScope::ID], 'XmlHttpRequest' => true])]
class ImageController extends StorefrontController
{
    public function __construct(
        private readonly HttpClientInterface $client,
        private readonly SystemConfigService $systemConfigService
    ) {
    }
    #[Route(
        path: '/image',
        name: 'frontend.image.show',
        methods: ['GET']
    )]
    public function showImage(SalesChannelContext $context): Response
    {
        $apiAccessKey = $this->systemConfigService->get('AcademyStorefrontController.config.apiAccessKey', $context->getSalesChannelId());
        $apiProvider = $this->systemConfigService->get('AcademyStorefrontController.config.apiProvider', $context->getSalesChannelId());
        $response = $this->client->request('GET', 'https://api.'.$apiProvider.'.com/v1/images/search', [
            'headers' => [
                'x-api-key' => $apiAccessKey
            ]
        ]);
        $data = $response->toArray();
        $imageUrl = $data[0]['url'];
        return $this->renderStorefront('@AcademyStorefrontController/storefront/page/image.html.twig', [
            'imageUrl' => $imageUrl
        ]);
    }
}
ImageController extends the StorefrontController which gives you helper functions like renderStorefront().ImageController is set to storefront and the route is marked as AJAX-only (XmlHttpRequest: true).HttpClientInterface and SystemConfigService from the DI Container.showImage() is bound to the GET request with the path /image, The route name is frontend.image.show, where you can call it from JS or Twig.SystemConfigService gets the apiProvider and the API key from the plugin config.imageUrl as a parameter to show the view.In the following, you get more detailed information of the parts.
Attributes are a new feature in PHP 8. They are used to define metadata for classes, methods, and properties. In this case, we define the route for the showImage method.
#[Route(defaults: [PlatformRequest::ATTRIBUTE_ROUTE_SCOPE => [StorefrontRouteScope::ID], 'XmlHttpRequest' => true])]Shopware comes with four route scopes:
storefront -> StorefrontRouteScope::IDstore-api -> StoreApiRouteScope::IDapi -> ApiRouteScope::IDadministration -> AdministrationRouteScope::IDSince we are creating a storefront controller, we should use the storefront route scope. The xmlHttpRequest attribute is set to true, which means that the route is only available for AJAX requests. The xmlHttpRequest is set to true as we want to open the image in a modal window.
You should always use the constant for the route scope, as it is more readable and less error-prone than using a string.
The method showImage also has a route attribute. This is the route that will be called when the link is clicked in the storefront.
It defines a readable path, a name, and the HTTP method. The path is /image, the name is frontend.image.show, and the method is GET.
#[Route(
        path: '/image',
        name: 'frontend.image.show',
        methods: ['GET']
    )]SystemConfigServiceThe controller uses the SystemConfigService to fetch the optional API access key and provider from the plugin configuration. Plugin configurations are stored in the database and can be accessed via the SystemConfigService.
We are using two configuration input fields, the important one is the apiProvider field. It is a single-select field with two options: thecatapi and thedogapi.
So you can choose if you are a cat or dog person, based on that you will get a random dog or cat image.
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
    <card>
        <title>API settings</title>
        <input-field type="single-select">
            <name>apiProvider</name>
            <label>Are you a cat or dog person?</label>
            <options>
                <option>
                    <id>thecatapi</id>
                    <name>I am a cat person</name>
                </option>
                <option>
                    <id>thedogapi</id>
                    <name>I am a dog person</name>
                </option>
            </options>
            <defaultValue>thedogapi</defaultValue>
        </input-field>
        <input-field type="password">
            <name>apiAccessKey</name>
            <label>Access Key</label>
            <helpText>You don't need an API key to get a single image. This input is optional for > 10 images. https://developers.thecatapi.com</helpText>
        </input-field>
    </card>    
</config>If you want to learn more about plugin configurations, check out our plugin configuration unit, which is part of this learning path.
HttpClientInterfaceWe are using the HttpClientInterface to fetch the image from the API. The HttpClientInterface is a Symfony component that allows you to send HTTP requests.
The route import is part of the routes.xml file. This file is located in the src/Resources/config directory of your plugin.
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
        https://symfony.com/schema/routing/routing-1.0.xsd">
    <import resource="../../Storefront/Controller/**/*Controller.php" type="attribute" />
</routes>This tells the system where to find the controller. The type="attribute" is important here, as it tells Shopware that any routes declared in the controller class are defined as attributes.
If you don’t know what attributes are, don’t worry. It is a new feature in PHP 8 and used to define metadata for classes, methods, and properties. Check out the Symfony Docs for more information.
The service definition is part of the services.xml file. This file is located in the src/Resources/config directory of your plugin.
It is one of the key files in your plugin, as it tells the system which classes are services and how they should be instantiated.
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
  <services>
    <service id="ShopwareAcademy\StorefrontController\Storefront\Controller\ImageController" public="true">
      <argument type="service" id="http_client"/>
      <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
      <call method="setContainer">
        <argument type="service" id="service_container"/>
      </call>
    </service>
  </services>
</container>In this file, we define the ImageController as a service. We pass the HttpClientInterface and the SystemConfigService as arguments to the constructor.
We also call the setContainer method to define the container service respectively.
Normally, in the StorefrontController, the setTwig method is also included beneath the setContainer method. However, since Shopware 6.7.0.0, the setTwig method is removed from the StrorefrontController.
If you want to debug a route, type bin/console debug:router YOUR_ROUTE_NAME in your console. It will validate first if the route exists and then show you the route configuration. Note that your plugin must be installed one time, otherwise your custom route will not be recognized from the system.
+--------------+----------------------------------------------------------------------------------+
| Property     | Value                                                                            |
+--------------+----------------------------------------------------------------------------------+
| Route Name   | frontend.image.show                                                              |
| Path         | /image                                                                           |
| Path Regex   | {^/image$}sDu                                                                    |
| Host         | ANY                                                                              |
| Host Regex   |                                                                                  |
| Scheme       | ANY                                                                              |
| Method       | GET                                                                              |
| Requirements | NO CUSTOM                                                                        |
| Class        | Symfony\Component\Routing\Route                                                  |
| Defaults     | XmlHttpRequest: true                                                             |
|              | _controller: StorefrontPlugin\Storefront\Controller\ImageController::showImage() |
|              | _routeScope: array (0 => 'storefront',)                                          |
| Options      | compiler_class: Symfony\Component\Routing\RouteCompiler                          |
|              | utf8: true                                                                       |
+--------------+----------------------------------------------------------------------------------+
The template is a twig file used to render the output of the controller. It is located in the src/Resources/views/storefront/page directory of your plugin.
The path to that file is defined via the controller class method renderStorefront as seen in the controller example above (src/Resources/views/storefront/page/image.html.twig).
{% block base_content %}
    <h1>Some random image</h1>
    <img src="{{ imageUrl }}" alt="test" style="max-width: 70%;">
{% endblock %}To display the output of your custom controller in the storefront, you need to create a link to its route in a Twig template.
In Shopware, you override or extend a template by mirroring their folder structure and file name inside your plugin.
This means, if the original file (located under vendor/shopware) is at storefront/Resources/views/storefront/component/buy-widget/buy-widget.html.twig, you must create a file with the same name and path in your plugin: src/Resources/views/storefront/component/buy-widget/buy-widget.html.twig.
So the full path is: [shop_root]/custom/plugins/[your_plugin]/src/Resources/views/storefront/component/buy-widget/buy-widget.html.twig.
{# File is located in src/Resources/views/storefront/component/buy-widget/buy-widget.html.twig  #}
{% sw_extends '@Storefront/storefront/component/buy-widget/buy-widget.html.twig' %}
{% block buy_widget_ordernumber_container %}
    {{ parent() }}
    <a href="{{ path('frontend.image.show') }}" class="open-image-modal" data-ajax-modal="true"
       data-url="{{ path('frontend.image.show') }}">View Image</a>
{% endblock %}Since Shopware uses plugins and themes that you can sort in a hierarchy, it has its own way to extend templates. You have to use sw_extends instead of extends tag to extend a template from a plugin or theme.
This will create a link in the buy widget that opens a modal window.
The data-ajax-modal="true" attribute tells the system to open the link in a modal window. The data-url attribute tells the system which route to call when the link is clicked.
In this learning unit, you have learned:
services.xml file.routes.xml file.SystemConfigService and HttpClientInterface within a controller.With this, you have a practical foundation to start developing your own backend features and extend the storefront with custom controllers.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/app-configurations

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
In this learning unit, you will learn how to add app configurations to your Shopware App. App configurations allow you to predefine values or load default data during installation. This can save time and reduce manual setup steps.
Imagine you are developing a shipping provider app. Adding the shipping methods with a plugin can be cumbersome – you need to add the shipping methods manually in the Shopware administration. To make this process easier, you can add a configuration file to your App that contains the shipping methods. This way, the user can easily select the predefined shipping methods directly in the Shopware administration.
manifest.xml File to Your AppEvery Shopware App starts with a manifest.xml file. This file defines the app’s metadata, permissions, and configuration setup. It is located in the root directory of your App.
The basic manifest.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/Framework/App/Manifest/Schema/manifest-2.0.xsd">
    <meta>
        <name>ShippingApp</name>
        <label>Shipping App</label>
        <description>This app adds 2 new shipping methods</description>
        <author>shopware AG</author>
        <copyright>(c) shopware AG</copyright>
        <version>1.0.0</version>
        <license>MIT</license>
    </meta>
</manifest>For an in-depth explanation of the manifest.xml file, please refer to the official Shopware documentation.
In this learning unit, we will not cover the entire manifest.xml file, but focus specifically on the configuration section.
Shopware Apps support predefined XML tags such as shipping-methods and shipping-method. These tags allow you to define new shipping methods directly in the App’s manifest.xml file without writing additional code.
This is particularly useful for shipping provider apps, as they often need to register custom shipping methods during installation.
Here is how you can add shipping methods to the app configuration:
<shipping-methods>
    <shipping-method>
        <!-- The identifier should not change after the first release -->
        <identifier>FastShippingMethod</identifier>
        <name>Fast shipping method</name>
        <delivery-time>
            <!-- Requires a new generated UUID for your new delivery time -->
            <id>c8864e36a4d84bd4a16cc31b5953431b</id>
            <name>From 1 to 2 days</name>
            <min>1</min>
            <max>2</max>
            <unit>day</unit>
        </delivery-time>
    </shipping-method>
    <shipping-method>
        <!-- The identifier should not change after the first release -->
        <identifier>CargoShippingMethod</identifier>
        <name>Cargo shipping method</name>
        <delivery-time>
            <!-- Requires a new generated UUID for your new delivery time -->
            <id>46177c36a84b418b8ae1a22028aeb1c5</id>
            <name>From 1 to 2 weeks</name>
            <min>1</min>
            <max>2</max>
            <unit>week</unit>
        </delivery-time>
    </shipping-method>
</shipping-methods>Avoid changing the identifier after the App is released. If you do so, Shopware will treat it as a new shipping method and create a duplicate entry, which you will have to remove manually.
Great, we added two shipping methods that can be selected in the Shopware administration. This will make the process of adding shipping methods easier for the user.
For a complete list of all available tags within shipping methods, please refer to the official Shopware documentation.
Besides shipping methods, you can also define payment providers and tax providers via the manifest.xml file.
After adding or changing configurations in your App, you need to refresh the App, so Shopware can re-read the updated manifest.xml file. It is done in two steps:
Step 1: You have to increase the version number manually in the manifest.xml file. Only then Shopware detects the change and re-reads the file. For example, update the version number from 1.0.0 to 1.0.1.
Before:
<version>1.0.0</version>After:
<version>1.0.1</version>Step 2: Now refresh your App using the following command in your shop’s directory:
bin/console app:refreshThis command updates the App’s configuration and re-registers any changes defined in the manifest.xml file in the database.
Here is a list of all App-related CLI commands available in Shopware:
Available commands for the "app" namespace:
  app:activate            Activates an app
  app:create              Creates an app skeleton
  app:deactivate          Deactivates an app
  app:install             Installs an app
  app:refresh             [app:update] Refreshes an app
  app:uninstall           Uninstalls an app
  app:update              Refreshes an app
  app:url-change:resolve  Resolves app url changes
  app:validate            Validates an appBefore refreshing your App, you can run the bin/console app:validate command to check if your manifest.xml file is valid. This helps you catch syntax or schema errors early.
In this learning unit, you have learned:
manifest.xml file contains the configuration section.With this knowledge, you have a solid overview of how to add App configurations which fundamentally differ from plugins.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/course/configuration-and-settings

manifest.xml file and how they differ from plugins.Configuring your Shopware instance is a crucial part of the development process. This course will guide you through how to manage configuration and settings of your Shopware instance from a developer’s perspective.
You will explore how to configure your Shopware instance to suit your needs, adapt its behavior, and manage settings across different environments.
Everything starts with the environment variables. They are used to store sensitive information such as database credentials, API keys, and other configuration values. The .env file is not committed to the repository.
System configurations define how your Shopware instance behaves at runtime, for example, whether certain features are enabled or disabled. They can be managed via the administration, Admin API, or static configuration files.
Plugins and Apps can provide their own configuration interfaces. These allow developers and merchants to tailor functionality directly within the administration panel, once the extension is installed and activated.
Learn how to configure, override, and manage environment variables in Shopware for different environments.
Learn how to configure and manage system configurations in Shopware using the Admin API or static configuration files.
Learn how to add configuration fields to your Shopware plugin to make it more flexible, reusable, and customizable.
Learn how to add configuration fields to your Shopware App to make it more flexible, reusable, and customizable.
Learn how to manage configurations in Shopware, from environment variables to system settings and extension configuration.
Sign up to enroll and track your progress on this course
Sign up nowInspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/system-configurations

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
System configurations in Shopware define how the system behaves and how it interacts with the user. They are used to configure various aspects of the system, such as the appearance of the storefront, the behavior of the administration, and the performance of the system without changing the code.
In practice, system configurations act as a bridge between flexibility and control. You can change them dynamically through the administration or Admin-API, or define them statically to ensure consistent behavior across environments. As a developer, understanding system configurations allows you to adapt Shopware’s behavior quickly without touching the codebase.
To explore all system configurations directly, run the following query in your database:
SELECT * FROM system_config;This table includes both core settings and plugin configurations – for example, the SwagPaypal flag (SwagPayPal.settings.sandbox).
Imagine you have a Shopware shop, and you want to disable the “Buy” button in the product listing since your products are quite complex, and you want the customer to check out the product detail page.
You can do this by setting the system configuration core.listing.allowBuyInListing to false. This way, the “Buy” button will not be displayed in the product listing, and customers will have to click on the product to see the details and buy it.
You can find the setting in the administration panel under Settings > Shop > Products (https://YOUR_SHOP_DOMAIN/admin#/sw/settings/listing/index).
It is a simple toggle and can be true or false. Per default, it is set to true.
You can also configure system settings via the Admin API. In practice, you can use two different approaches.
Use this variant when you already know the exact configuration key that you want to update.
POST /api/_action/system-config
Content-Type: application/json
{
  "core.listing.allowBuyInListing": false
}You can also update a system configuration via PATCH if you already know the ID of the matching system_config entity (in this example, the ID of the system config for core.listing.allowBuyInListing).
This variant is a bit more technical because it works with the entity ID instead of the configuration key: Update system configuration
PATCH /api/system-config/<system-config-id>
Content-Type: application/json
{
  "configurationValue": false
}If you want to use the PATCH variant, you first need the ID of the matching system_config entity. You can get it from the database in the format expected by the API with this query:
SELECT LOWER(HEX(id)) AS id
FROM system_config
WHERE configuration_key = 'core.listing.allowBuyInListing';This endpoint is different from the POST endpoint /api/_action/system-config. The POST example above updates a configuration by key, while this PATCH example updates an existing system_config entity by ID.
If you do not need to work with a specific system_config entity, the POST variant is usually the simpler option.
.env or Config YAMLThis feature is available since Shopware 6.6.4.0.
You can also set the core.listing.allowBuyInListing setting statically via the .env file or the config/packages/shopware.yaml file. Static configuration is ideal when:
shopware:
  system_config:
    default:
      core.listing.allowBuyInListing: true
    # Disable it for the specific sales channel
    0188da12724970b9b4a708298259b171:
      core.listing.allowBuyInListing: falseStatic configuration has a higher priority over changes made via the administration. If a value is defined in shopware.yaml, it cannot be changed from the administration.
For all possible options and more information, check out the official documentation.
You may notice examples in the documentation where system configuration values are referenced from .env variables.
Shopware does not automatically derive environment variable names from the configuration key.
When using .env values inside config/packages/shopware.yaml, you are simply using Symfony’s standard env() processor, for example:
shopware:
  system_config:
    default:
      core.listing.allowBuyInListing: '%env(bool:ALLOW_BUY_IN_LISTING)%'In this example:
ALLOW_BUY_IN_LISTING is not generated by Shopware.core.listing.allowBuyInListing.You can define any environment variable name you want. What matters is:
core.listing.allowBuyInListing..env file.Example:
shopware:
  system_config:
    default:
      core.listing.allowBuyInListing: '%env(bool:DISABLE_BUY_IN_LISTING)%'Then your .env file must contain:
DISABLE_BUY_IN_LISTING=trueThis means:
config/packages/shopware.yaml..env variables are optional and only used when you explicitly reference them using %env()%.In this learning unit, you have learned:
system_config table)..env file or the config/packages/shopware.yaml file.core.listing.allowBuyInListing setting controls the visibility of the “Buy” button in the product listings.With this knowledge, you can confidently manage system behavior in Shopware; either dynamically via API or statically for stable, environment-specific setups.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/introduction-to-the-basic-architectural-pattern

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
Before we start, it’s important to know that Shopware is built on Symfony and therefore follows its architectural patterns. In this learning unit, you will explore the key components that form the backbone of Shopware’s architecture.
 Keep in mind that this is not the complete picture, but a simplified overview to help you get oriented.
Let’s start with a short overview of the architectural pattern behind Shopware. The following components are essential building blocks:
In Symfony/Shopware, services, controllers, and subscribers are registered in the Dependency Injection Container (via services.xml or autowiring) and then injected where needed.
Services are plain PHP classes that contain your business logic in methods. These are reusable helpers that can be called from controllers, event subscribers, and other services.
 Using services, you can centralize your business logic in one place. This makes your project easier to maintain and avoids code duplication by reusing the same logic in multiple places.
Controllers are one possible entry-point to trigger your business logic via an HTTP-Request. They usually handle incoming HTTP requests, delegate logic to services, and return a response.
An event subscriber is a plain PHP class, where you can hook into predefined Shopware or Symfony events by implementing your own methods. Event subscribers are also entry points to add your business logic at the right point in the workflow without losing the core logic.
 For example, if you want to add logic to the product detail page, you can listen to the ProductPageLoadedEvent.
Before DI, you had to specify what you need and how to build it in every new call inside each PHP class. This led to poor readability, maintainability, and testability.
Every time you needed a service, you had to build the what and how manually.
<?php declare(strict_types=1);
    $orderService = new OrderService(
            new Mailer(new SmtpClient('host', 'user', 'pass')),
            new Logger('/var/logs/app.log')
    );
?>This meant that every place using the OrderService (what) needed a definition of how to build Mailer and Logger. The result was duplicated code and a codebase that was hard to maintain.
In PHP, you now only write what you need. The how is defined in the DI-Container, Symfony/Shopware will inject the dependencies automatically into your constructor.
 Dependency Injection is managed via services.xml. In this file, you register each PHP class you create (e.g., subscriber classes, controller classes, service classes, etc.) and define which constructor arguments they require.
<?php declare(strict_types=1);
class ExampleController extends StorefrontController
{
    private EntityRepository $orderRepository;
    
    public function __construct(EntityRepository $orderRepository)
    {
        $this->orderRepository = $orderRepository;
    }For now, focus on understanding the concept. We will cover later how to implement and use dependency injection in code.
In this learning unit, you have learned:
With this, you have a solid foundation to understand how Shopware’s backend is structured and how its components work together behind the scenes.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/environment-variables

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
.env files.Before writing any code, it’s important to understand why environment variables exist and how to use them. In Shopware and also in any Symfony project, environment variables are the foundation for flexible and secure configuration. They allow you to:
This helps you ensure that your project remains both secure and portable
Every Symfony project, including Shopware, uses .env files to store environment variables. Environment variables are key-value pairs that define configuration such as database credentials, API keys, or URLs outside your codebase. You can create multiple .env files for different environments:
| File Name | Purpose | 
|---|---|
| .env | Default file. Contains the default values for the environment variables. Used as template for others. | 
| .env.local | Developer-specific overrides (e.g., local DB credentials). | 
| .env.prod.local | Overrides for production configuration. Contains the values for the environment variables that are specific to the production environment. | 
| .env.test.local | Overrides for testing configuration. Used in environments running tests (unit tests, integration tests, application-tests). | 
In modern Symfony/Shopware projects, the .env file is usually committed to version control and acts as a baseline with non-sensitive defaults. Do not put real secrets into .env. Instead, store secrets and machine/environment-specific overrides in .env.local / .env.*.local (which are typically excluded via .gitignore) or provide them via your hosting environment / secret management solution.
When another developer clones the repository, they can copy the .env file to .env.local and define their own credentials and secrets without exposing sensitive data in version control.
You can find more information about environment variables in the official Symfony documentation.
Never commit files containing credentials or secrets (like the .env.local file) to version control. Always keep sensitive data out of repositories.
A common change within the .env file is the MAILER_DSN variable. Let’s say your shop is running in production, and you want to send emails to your customers. You can set the MAILER_DSN variable to use the SMTP mailer to send emails. This way, you can send emails to your customers without any additional setup.
You can set the MAILER_DSN variable in the .env.prod.local file: Open the .env.prod.local file and add or adjust the following line:
MAILER_DSN=smtp://username:password@smtp.gmail.com:587?encryption=tls&auth_mode=loginAnd clear the cache by running the following command in your shop’s root directory:
bin/console cache:clearNow your Shopware shop will use Gmail’s SMTP server to send emails.
Even though Shopware allows email settings via the Admin Panel (Settings -> Email Settings), it’s recommended to set the MAILER_DSN in the .env file, especially for production environments, to ensure consistency and to prevent accidental overwrites.
Now that you have seen a practical example, let’s take a closer look at the most common environment variables in Shopware.
Below you will find the common environment variables.
| Variable | Explanation | 
|---|---|
| APP_ENV | Specifies the current environment, e.g., dev,prod,test. Affects caching, logging, error pages, etc. | 
| APP_URL | The accessible URL of the shop. Important for generated links and redirects. | 
| APP_SECRET | Used internally for hashes, tokens, etc. Should not be changed unless intentional, otherwise sessions and other feature may break. | 
| INSTANCE_ID | Unique ID of the Shopware installation. Used for the shop and plugin verification. | 
| DATABASE_URL | Connection URL for the MySQL or MariaDB database. Format mysql://user:password@host:port/databasename. | 
| MAILER_DSN | Configuration for sending emails via Symfony Mailer. Default null://localhost. | 
| SHOPWARE_ES_ENABLED | Enables/Disables OpenSearch/Elasticsearch. Default 0 | 
| SHOPWARE_HTTP_CACHE_ENABLED | Enable/Disable the HTTP cache. Default 1. | 
| BLUE_GREEN_DEPLOYMENT | Enables special mechanisms for deployments in production environments. Default 0. | 
If you want to see the full list of the environment variables, go to this official document.
The Symfony Mailer component is used to send emails from the application. It can be configured to use different mailers for sending emails. The default mailer is the null mailer, which does not send any emails. This is a good choice for development environments, as it does not require any additional setup.
# Default mailer
MAILER_DSN=null://nullThe most common mailer is the smtp mailer, which sends emails using an SMTP server. This is a good choice for production environments, as it is fast and reliable.
Here is an example of a free and easy to set up SMTP server with GMAIL:
MAILER_DSN=smtp://username:password@smtp.gmail.com:587?encryption=tls&auth_mode=loginUsing a private email server is not recommended in production. It is better to use a third party email provider.
If you want to check out the other environment variables, you can find them in the official Developer documentation.
In this learning unit, you have learned:
.env files.MAILER_DSN.With this knowledge, you can confidently configure and manage your Shopware environments for development, staging, and production.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/course/testing-and-quality-assurance

Testing and Quality Assurance (QA) are crucial parts of the development process. They ensure that your projects remain stable, maintainable, and ready for future updates.
This course will guide you through the testing and quality assurance of your Shopware instance from a developer’s perspective. You will learn how to test your Shopware instance to ensure that it works as expected and how to ensure the quality of your code.
Learn why testing matters in Shopware, how unit tests and end-to-end tests differ, and when to use PHPUnit, Jest, and Playwright.
Learn how to ensure high code quality in Shopware through testing, reviews, tools, and continuous improvement.
Learn how to set up PHPUnit for a Shopware plugin, test a console command, and run the tests locally and in CI.
Learn how to set up, write, and run tests in Shopware to ensure code stability and long-term quality.
Sign up to enroll and track your progress on this course
Sign up nowInspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/course/basic-plugin-development

In this course, you will learn the basics of Shopware plugin development. You will learn how to create a plugin, understand the plugin structure, and how to manipulate the shop instance programmatically.
Get an overview of the basic architectural pattern used in Shopware.
In this course, you will learn how to get started with Shopware backend development based on a storefront controller implemented as a service.
Learn how to extend products with custom data programmatically using custom fields and understand when to use properties or custom entities.
Learn how to debug your code within Shopware, trace issues effectively, and apply best practices for troubleshooting.
Learn the fundamentals of Shopware plugin development, including architecture, controllers, data extension, and debugging techniques.
Sign up to enroll and track your progress on this course
Sign up nowInspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/path/shopware-backend-development-essentials

This is the right learning path for you if you want to start developing powerful backend extensions with Shopware!
It introduces you to the core architecture, development concepts, and quality practices that form the foundation of Shopware backend development. You will learn:
Tailored for developers eager to dive into Shopware backend development, this learning path will equip you with the skills to build robust, maintainable, and customized solutions for Shopware projects. Note, whenever we mention “Shopware,” we are referring to Shopware 6.
Before diving into this learning path, make sure you have:
Learn the fundamentals of Shopware plugin development, including architecture, controllers, data extension, and debugging techniques.
Learn how to manage configurations in Shopware, from environment variables to system settings and extension configuration.
Learn how to set up, write, and run tests in Shopware to ensure code stability and long-term quality.
Sign up to enroll and track your progress on this learning path
Sign up nowInspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/intro-testing-shopware

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
Testing is not only a quality topic. In Shopware projects, it directly affects how safely you can change code, ship new features, and keep extensions stable over time.
If your plugin or App grows, manual checks quickly stop being enough. A small change in business logic, storefront behavior, or the administration can break something that used to work.
In this learning unit, you learn why testing matters, how the main test types differ, and which tools Shopware uses for unit and end-to-end testing.
Imagine your plugin or App is feature-complete and works well in your local environment. The next release changes a service, updates a template, or adds a new storefront interaction. Without tests, you often notice problems only after manual QA or after release.
Tests reduce that risk. They help you verify expected behavior early and give you more confidence when the codebase changes.
That way, testing becomes part of reliable delivery, not just a final check before launch.
Testing often looks expensive at first because it adds work early in the project. In practice, it usually saves time later by reducing regressions, manual retesting, and risky releases.
If testing is not part of your development process yet, prepare a short pitch with numbers and facts:
You do not need to present testing as a perfect solution. A more realistic message is enough: Testing lowers risk, improves feedback, and helps teams scale development more safely.
Some developers follow a Test-Driven Development (TDD) approach, where tests are written before the actual implementation.
However, in Shopware projects and plugin development, it is often more practical to start testing once the architecture and key components are stable enough to test deliberately.
The next question is not which framework to install first. The more useful question is: What exactly do I want to verify?
This distinction keeps testing efficient. Small logic problems should not require a full browser test, and user journeys cannot be proven by isolated unit tests alone.
Unit tests are used to test individual units of code, such as functions or classes. They run in isolation from the rest of the codebase and should be fast to execute. Their main goal is to ensure that small building blocks or your application behave as expected.
Use them when you want fast feedback on business logic, service behavior, or small frontend units.
Shopware uses PHPUnit for backend unit tests. If you created your plugin using the bin/console plugin:create <PluginName> command, the necessary PHPUnit configuration is already set up for you.
This is the right tool for testing PHP classes, services, and backend logic in your extension.
You will find all necessary information about writing and running unit tests, setting up integration tests, and mocking services in the official documentation.
If you want to dive deeper into PHP unit testing (test structure, best practices, and more examples), continue with the successor intermediate learning path.
A good next reference point is the Unit Tests learning unit.
You can also write unit tests using Jest for frontend development (storefront and administration).
Jest is useful when you want to test Vue components, custom JavaScript functions, and storefront plugins in isolation. It also provides features such as mocking and snapshot testing.
You can find more information in the official documentation:
End-to-end tests are used to test the entire application flow or a specific feature. They simulate real user interactions with the application.
For example, you might verify that a customer can open a product detail page, click a button, complete a checkout step, or see the expected result in the UI.
Use this test type when the question is no longer “Does this function return the right value?” but “Does this feature work from the user’s point of view?”
Shopware uses Playwright for end-to-end testing. Playwright allows you to automate browser actions, simulate user journeys, and verify UI behavior across different pages and browsers.
Shopware provides an official acceptance test suite, which includes preconfigured tests and utilities to kickstart your testing setup.
This is the right choice when you want to test real browser behavior across the storefront or administration.
For detailed installation and usage instructions, check the official developer documentation and the dedicated Playwright learning unit. They show how to install the test suite and run the first tests.
Older Shopware materials may still mention Cypress, but that setup is now legacy. For new work, use Playwright as the default end-to-end testing framework.
If your project does not have tests yet, do not try to automate everything at once.
A good starting point is usually:
That way, you build coverage where it gives the highest value first.
In this learning unit, you have learned:
With this knowledge, you can now choose the right test type more deliberately and build a testing strategy that fits your Shopware project.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/plugin-configuration

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
In this learning unit, you will learn how to create configuration fields for your Shopware plugin. By using configuration fields, your plugin becomes more flexible, reusable, and customizable for different projects and environments.
Imagine you are developing a plugin that should display a banner on the homepage on a specific date, for example, Black Friday. To make this feature configurable, you can add a date input field to your plugin configuration. This allows the merchant to set the date directly in the administration panel without touching any code.
config.xml File to Your PluginTo add configurations to your plugin, you need to create a config.xml file in the src/Resources/config directory of your plugin. This file will contain all the configurations for your plugin.
└── plugins
    └── EventPlugin
        ├── src
        │   ├── Resources
        │   │   └── config
        │   │       └── config.xml 
        │   └── EventPlugin.php
        └── composer.jsonIn the beginning, the config.xml file should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
</config>date FieldA date field is a common input field type that allows the user to select a date from a calendar. To add a date field to your plugin, you need to add the following code to your config.xml file:
<input-field type="date">
    <name>specialEventDate</name>
    <label>Date</label>
    <defaultValue>2025-11-29T00:00:00</defaultValue>
</input-field>Your config.xml file should now look like this:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
  <card>
    <title>Minimal configuration</title>
    <input-field type="date">
      <name>specialEventDate</name>
      <label>Date</label>
      <defaultValue>2025-09-29T00:00:00</defaultValue>
    </input-field>
  </card>
</config>Great, we can now check in our PHP code for this value, and if the date is today, we can show the banner. However, this would be very static.
How about adding a translatable text field to the configuration, so the user can set the banner text in multiple languages?
A snippet is a translatable piece of text. It allows you to manage and display text content in multiple languages, making your plugin multilingual and user-friendly – a must-have for international shops. They are often used for labels, messages, and other text elements in the Shopware Frontend and Backend.
To add a snippet field to your plugin, use the sw-snippet-field component:
<component name="sw-snippet-field">
  <name>bannerText</name>
  <label>Text for the banner</label>
  <snippet>eventPlugin.banner.text</snippet>
</component>At this stage, your config.xml file should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
  <card>
    <title>Minimal configuration</title>
    <input-field type="date">
      <name>specialEventDate</name>
      <label>Date</label>
      <defaultValue>2025-09-29T00:00:00</defaultValue>
    </input-field>
    <component name="sw-snippet-field">
      <name>bannerText</name>
      <label>Text for the banner</label>
      <snippet>eventPlugin.banner.text</snippet>
    </component>
  </card>
</config>Components are custom input fields that can be used to create complex configurations. In this case, we are using the sw-snippet-field component to create a snippet field for the banner text across different languages.
Perfect, and what about the colors? Let’s add a color picker to the configuration so we can also use the banner in different colors (for example, red for Valentine’s Day).
To add a color picker field to your plugin, you need to add the following code to your config.xml file:
<input-field type="colorpicker">
  <name>bannerBackgroundColor</name>
  <label>Color for the banner</label>
  <defaultValue>#000000</defaultValue>
</input-field>
<input-field type="colorpicker">
  <name>bannerTextColor</name>
  <label>Text color for the banner</label>
  <defaultValue>#FFFFFF</defaultValue>
</input-field>At this stage, your config.xml file should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
  <card>
    <title>Minimal configuration</title>
    <input-field type="date">
      <name>specialEventDate</name>
      <label>Date</label>
      <defaultValue>2025-09-29T00:00:00</defaultValue>
    </input-field>
    <component name="sw-snippet-field">
      <name>bannerText</name>
      <label>Text for the banner</label>
      <snippet>eventPlugin.banner.text</snippet>
    </component>
    <input-field type="colorpicker">
      <name>bannerBackgroundColor</name>
      <label>Color for the banner</label>
      <defaultValue>#000000</defaultValue>
    </input-field>
    <input-field type="colorpicker">
      <name>bannerTextColor</name>
      <label>Text color for the banner</label>
      <defaultValue>#FFFFFF</defaultValue>
    </input-field>
    
  </card>
</config>With this, the merchant can customize both the background color and the text color for the banner directly from the administration panel.
You can add more complex fields such as:
For example, you could create a multi-select-field to show the banner only on desktop and tablet devices.
If you want to explore all options, please visit the official developer documentation.
Whenever a plugin configuration is created or its value has been updated, Shopware saves the values in the system_config table.
You can find your configuration entries by searching for your plugin prefix (usually the technical plugin name) in the configuration_key column.
In this learning unit, you have learned:
config.xml) to your plugin.With this knowledge, you can now create plugins that are easily configurable and adaptable to various business requirements.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/setting-up-and-running-phpunit-tests

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
TestBootstrapper and autoload-dev.phpunit.xml file.Great to see you have made it this far! We know that writing tests is not the most exciting part of development, but it is a crucial step for maintaining code quality, stability, and confidence in your project.
In this learning unit, you will learn how to write and run PHPUnit tests for your Shopware plugin, both locally and in a CI environment.
To not bore you with too much theory, we will focus on a real-world example to show you how to write a PHPUnit test for a Shopware plugin. If you want to dive deeper into the topic, check out the official PHPUnit documentation.
Imagine you have a custom command in your plugin that should return a specific value or trigger a certain workflow. You can write a PHPUnit test to verify that the command returns the expected value. This way, you can ensure that your command works as expected even after future changes.
To write tests in Shopware, you first need a plugin that contains a testable component, for example, a command. When working with PHPUnit, each plugin requires:
composer.json file, so PHPUnit knows where to find the tests.You can either create your own plugin from scratch or use the existing example to follow along.
Run the following command in your shop’s root directory to create a new plugin:
bin/console plugin:create MyPhpUnitPluginWhen prompted, select the Command option to create a testable command class out of the box.
If your generated plugin does not contain a command yet, create a small example command first. Add the file src/Command/ExampleCommand.php in your plugin:
<?php declare(strict_types=1);
namespace MyPhpUnitPlugin\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
    name: 'swag-commands:example',
    description: 'Demonstrates a simple testable command',
)]
class ExampleCommand extends Command
{
    // Actual code executed in the command
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('It works!');
        // Exit code 0 for success
        return self::SUCCESS;
    }
}If your plugin has a different namespace, replace MyPhpUnitPlugin with your plugin namespace. The test in this unit needs a real class to test. If the command class is missing, the test cannot run successfully.
If you prefer to start with a working example, clone the following repository into the plugins folder ([shop_root]/custom/plugins):
git clone git@github.com:ShopwareAcademy/AcademyPhpUnit.git custom/plugins/AcademyPhpUnitThis example plugin already includes:
ExampleCommand).ExampleCommandTest).TestBootstrapper class and configuration.You can find a detailed explanation of PHPUnit setup and configuration in the official documentation.
Remember to install and activate the plugin independently of the option you chose by running the following commands in your shop’s root directory.
If you created your own plugin, use:
bin/console plugin:refresh
bin/console plugin:install MyPhpUnitPlugin --activate --clearCacheIf you cloned the example plugin, use:
bin/console plugin:refresh
bin/console plugin:install AcademyPhpUnit --activate --clearCacheNow you are ready to start writing your first PHPUnit test.
The examples below use AcademyPhpUnit because that is the name of the reference plugin. If you created MyPhpUnitPlugin, replace AcademyPhpUnit with MyPhpUnitPlugin in every related place: namespace, addActivePlugins, autoload-dev, test imports, and the phpunit.xml path.
The PHPUnit setup consists of four essential parts. Each part has its own purpose in ensuring that your tests run smoothly and can properly interact with your plugin:
| Part | Description | 
|---|---|
| The TestBootstrapper | A PHP class that prepares the Shopware test environment. | 
| The tests | PHP classes that contain your actual test cases and assertions. | 
| autoload-dev | A section in the composer.jsonfile that tells PHPUnit where to find your test classes. | 
| phpunit.xml | The PHPUnit configuration file that points to the bootstrap file and test directory. | 
Let’s take a closer look at each part.
The TestBootstrapper class is responsible for setting up the Shopware test environment. It ensures that all required services, plugins, and autoloading rules are available when running your tests.
Add a file named TestBootstrap.php to the tests directory of your plugin ([shop_root]/custom/plugins/[Your_Plugin]/tests).
<?php declare(strict_types=1);
use Shopware\Core\TestBootstrapper;
try {
    $loader = (new TestBootstrapper())
        ->addCallingPlugin()
        ->addActivePlugins('AcademyPhpUnit') // Replace it with your plugin name if different
        ->setForceInstallPlugins(true)
        ->bootstrap()
        ->getClassLoader();
} catch (Exception $exception) {
    throw new RuntimeException(
        'Could not bootstrap the PHPUnit test environment.',
        0,
        $exception
    );
}
$loader->addPsr4('AcademyPhpUnit\\Tests\\', __DIR__);The name inside addActivePlugins must match your plugin’s name exactly.
If you cloned the example repository, keep it as AcademyPhpUnit.
If you created your own plugin with plugin:create MyPhpUnitPlugin, use 'MyPhpUnitPlugin' instead.
If you created your own plugin, the same replacement is needed for the test namespace registration:
$loader->addPsr4('MyPhpUnitPlugin\\Tests\\', __DIR__);Explanation:
addCallingPlugin method registers your plugin as the one being tested.addActivePlugins method registers the plugins that should be available during testing (e.g., the plugin that contains the command you want to test).setForceInstallPlugins method ensures that all required plugins are automatically installed before running the tests.bootstrap method prepares the Shopware testing environment.getClassLoader method returns the autoloader used to load all required classes.addPsr4 method adds the AcademyPhpUnit\Tests namespace to the autoloader.try-catch block wraps bootstrap errors in a clearer exception message. This makes setup problems easier to understand when the test environment cannot be prepared.The tests are PHP classes that contain the actual test methods that verify your plugin’s behavior. Each test method should focus on one specific functionality or expected outcome.
Below is a simple example of a PHPUnit test for a Shopware command:
Add this test as tests/Command/ExampleCommandTest.php. The *Test.php file name is important because PHPUnit uses this naming pattern to discover test files.
Don’t forget to fix the namespace, based on what plugin name you have chosen.
<?php declare(strict_types=1);
namespace AcademyPhpUnit\Tests\Command;
use AcademyPhpUnit\Command\ExampleCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
class ExampleCommandTest extends TestCase
{
    public function testDescriptionIsCorrect(): void
    {
        $command = new ExampleCommand();
        $commandTester = new CommandTester($command);
        $commandTester->execute([]);
        $commandTester->assertCommandIsSuccessful();
        $this->assertStringContainsString('It works!', $commandTester->getDisplay());
        $this->assertSame('Demonstrates a simple testable command', $command->getDescription());
    }
}Explanation:
TestCase class from the PHPUnit framework, which provides assertion methods (e.g., assertSame) and test lifecycle hooks.CommandTester class simulates the running of the command and allows you to check its output, status, or behavior.assertCommandIsSuccessful() checks that the command finished with a successful exit code.assertStringContainsString() checks that the command printed the expected output.assertSame() ensures that the command’s description matches the expected value.You can write as many test methods as you need to cover different aspects of your command’s behavior.
The code example uses the AcademyPhpUnit namespace because it belongs to the reference plugin.
If you created your own plugin, update the namespace and import:
namespace MyPhpUnitPlugin\Tests\Command;
use MyPhpUnitPlugin\Command\ExampleCommand;Also make sure the file is named ExampleCommandTest.php, not only ExampleCommand.php. Otherwise, PHPUnit may finish with No tests executed! because it does not discover the test class.
As your plugin grows, tests may involve mocking dependencies or stubbing services to isolate functionality. Check out the Shopware testing guide for more examples.
autoload-dev SectionThe autoload-dev section in your composer.json file defines autoloading rules that are only used in development environments, such as during testing. This ensures that PHPUnit can locate and load your test classes automatically without requiring manual includes.
Add the section to your plugin’s composer.json, not to the project root composer.json:
"autoload-dev": {
    "psr-4": {
        "AcademyPhpUnit\\Tests\\": "tests/"
    }
}The psr-4 key defines the namespace mapping for your test classes. The namespace AcademyPhpUnit\\Tests\\ corresponds to the tests directory of your plugin. When running PHPUnit, Composer will automatically use this mapping to autoload your test classes. This setup keeps your production autoloading (autoload) separate from test autoloading (autoload-dev). This ensures that test classes are not included in production builds.
After modifying the autoload-dev section, you need to run composer dump-autoload from your shop’s root directory to update the autoloader and make sure that your test classes are recognized.
If you created your own plugin, use your own test namespace:
"autoload-dev": {
    "psr-4": {
        "MyPhpUnitPlugin\\Tests\\": "tests/"
    }
}phpunit.xml FileThe phpunit.xml file tells PHPUnit how to run the plugin tests. Add it to the root directory of your plugin, for example [shop_root]/custom/plugins/[Your_Plugin]/phpunit.xml.
For the example plugin, the file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd"
         bootstrap="tests/TestBootstrap.php"
         executionOrder="random">
    <source>
        <include>
            <directory>./src/</directory>
        </include>
    </source>
    <php>
        <ini name="error_reporting" value="-1"/>
        <server name="KERNEL_CLASS" value="Shopware\Core\Kernel"/>
        <env name="APP_ENV" value="test"/>
        <env name="APP_DEBUG" value="1"/>
        <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
    </php>
    <testsuites>
        <testsuite name="AcademyPhpUnit Testsuite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>If you created your own plugin, you can keep the same structure and only adjust the testsuite name, for example MyPhpUnitPlugin Testsuite. The important part is bootstrap="tests/TestBootstrap.php" because this is what prepares the Shopware test environment before PHPUnit discovers and runs your test classes.
Before running your tests, make sure you are using the correct project template and that PHPUnit is available in your environment.
The production template does not include the PHPUnit binary. If you are using it, or if vendor/bin/phpunit does not exist in your project, install PHPUnit manually:
composer require --dev phpunit/phpunit
composer dump-autoloadRun PHPUnit from your shop’s root directory, not from inside the plugin directory.
If you cloned the example plugin, use:
vendor/bin/phpunit -c custom/plugins/AcademyPhpUnit/phpunit.xmlIf you created your own plugin, replace the plugin folder name:
vendor/bin/phpunit -c custom/plugins/MyPhpUnitPlugin/phpunit.xmlThis command uses the PHPUnit configuration from the plugin and executes all tests defined there. If your plugin has a different folder name, adjust the path to your own phpunit.xml file.
The first test run prepares the Shopware test environment. This can create or reset the test database, run migrations, refresh plugins, install the example plugin, and clear caches.
This output can be long, but it is expected. The important part is the final PHPUnit result.
If PHPUnit reports deprecations but the test status is OK, the test itself still passed. Deprecations should still be reviewed because they may require future code or configuration updates.
If you encounter an error like: Base table or view not found: 1146 Table 'platform_test.app' doesn't exist, you need to set up the database for the tests. This can be done by executing the following command in your shop’s root directory: composer init:testdb.
If you run the tests in a Docker-based Shopware setup and see an error such as SQLSTATE[HY000] [2002] No such file or directory, check the database host in your test environment configuration.
If you use a Docker setup, check the database host from the environment where PHPUnit runs. For example, if PHPUnit runs inside the Shopware/PHP container, the database host must be reachable from that container.
This error usually does not mean that PHPUnit or the TestBootstrapper is broken. It often means that the PHP process running PHPUnit cannot reach the database.
In Docker, localhost means “inside this container”. If PHPUnit runs in the Shopware/PHP container and the database runs in another container, localhost is the wrong host. Use the database service name from your Docker Compose setup instead, for example database or mysql.
If the database really runs on your host machine, use the host address provided by your Docker setup, for example host.docker.internal where available. Using 127.0.0.1 can force a TCP connection instead of a MySQL Unix socket, but it still only works if the database is reachable from the environment where PHPUnit runs.
So first ask: “Where does PHPUnit run?” Then set the database host from that point of view.
To ensure that your plugin remains stable across all environments, it is best to run your PHPUnit tests in a Continuous Integration (CI) pipeline. This way, your tests run automatically on every pull request.
The example plugin uses a reusable GitHub Actions workflow from shopware/github-actions. This workflow provides predefined steps for setting up Shopware, preparing the database, installing the plugin, and running PHPUnit.
You will find the necessary configuration in the .github/workflows directory. In our example plugin repository, you can find the workflow file.
The workflow file contains the following important part:
jobs:
  phpunit:
    uses: shopware/github-actions/.github/workflows/phpunit.yml@main
    with:
      extensionName: ${{ github.event.repository.name }}
      shopwareVersion: trunkThe uses line tells GitHub Actions to reuse Shopware’s PHPUnit workflow. The extensionName value tells the workflow which plugin repository should be tested. The shopwareVersion value defines which Shopware version the test environment should use.
Make sure your CI environment includes all required dependencies (e.g., database, PHP extensions) and uses the same Shopware version as your local setup for consistent test results.
In this learning unit, you have learned:
phpunit.xml connects PHPUnit to the plugin bootstrap and test directory.CommandTester.phpunit.xml configuration.With this knowledge, you can confidently integrate PHPUnit tests into your Shopware development workflow and ensure your plugins remain stable and reliable.
If you want to go deeper into PHPUnit testing, continue with the Backend Development Intermediate learning path.
Well done! You have completed this course and with it the entire Backend Development Essentials learning path. You now have a solid foundation to start developing your own Shopware plugins.
From here, you can continue seamlessly with the Shopware Backend Development Intermediate learning path to deepen your knowledge.
Inspect discussions, ask questions and give feedback.
Sign up now

QUELLE: https://hub.shopware.com/learn/unit/maintaining-code-quality

Power-up your learning experience now
Benefit from progress tracking, gamification, individualized suggestions and community discussions.
Maintaining code quality is essential for the long-term success and stability of your project. It ensures that your code is readable, maintainable, and scalable; not only for you, but also for your team and future developers.
Code quality begins with a solid foundation. It may sound obvious, but using version control is still one of the most important practices in modern development. Always use Git to manage your source code, whether you are building an extension, Shopware App, or a Shopware Bundle.
Version control allows you to:
Always commit frequently and write clear, descriptive commit messages. This helps you and your team to understand the purpose behind each change.
Automated testing is a crucial part of maintaining code quality. It helps you catch bugs early in the development process, ensures your code works as expected, even after future changes.
There are different types of automated tests, each with its own purpose:
Automated tests serve as a safety net. They help you refactor with confidence, prevent bugs from reappearing, and ensure that your code is always working as expected.
Start small, begin with a few unit tests for critical logic. Over time, extend your coverage with integration tests and end-to-end tests to ensure stability across your entire application.
Code reviews are another essential part of maintaining code quality. They help you identify bugs early, improve code quality, and ensure that your code follows best practices. Code reviews should be done by your peers or a senior developer who can provide valuable feedback and suggest improvements.
A code review should always be seen as a learning opportunity, not as criticism. It is a great way to share knowledge, learn from others, and grow as a developer.
Encourage an open and respectful feedback culture. Constructive reviews lead to better code and stronger teamwork.
There are many tools available that help you analyze, enforce, and maintain code quality. They automatically check your code for common issues, enforce coding standards, and highlight potential bugs before they reach production. Some popular tools include:
Integrate these tools into your CI/CD pipeline or pre-commit hooks. This ensures consistent code quality across your team and prevents low-quality code from being merged.
Good documentation is a key part of maintaining high code quality. It ensures that you and other developers can easily understand your code, its purpose, and how to use or extend it. Well-written documentation not only saves time but also prevents misunderstandings and duplicate work.
It also helps new team members or external contributors to get started quickly, understand your project architecture, and follow established conventions and design decisions.
Keep your documentation close to the code, for example, in a README.md file, PHPDoc blocks, or inline comments. This ensures it stays up to date and relevant.
Continuous integration (CI) is a development practice that helps you catch bugs early in the development process and ensures that your project remains stable, and deployable at all times.
It involves automatically building, testing, and validating your code whenever changes are pushed to the repository, for example, when opening a pull request (merge request) or merging new features.
By integrating CI, you ensure that your codebase is always in a working state, reducing the risk of introducing bugs and improving team collaboration.
Set up CI pipelines (e.g., with GitHub Actions or GitLab CI, or Jenkins) to run tests and code quality checks automatically. This prevents broken code from being merged into the main branch.
Code quality guidelines are a set of rules and best practices that help you maintain a consistent, readable, and scalable codebase. They typically cover areas such as coding standards, naming conventions, file structure, and documentation style.
By following clear guidelines, you ensure that your team writes uniform code, making it easier to review, debug, and extend your project in the future.
Establish your code quality guideline at the very beginning of your project. Introducing them later in a large codebase can be very time-consuming and may require a lot of work.
Refactoring is the process of improving your code without changing its behavior. It helps you keep your code clean, maintainable, and scalable. Regularly refactoring your code can help you catch bugs, improve code quality, and ensure that your code is easy to maintain.
Refactoring should not be a one-time activity; schedule it regularly, for example, once per quarter. This way you can ensure that your codebase is always up to date and follows the latest best practices.
If you are publishing your extension or App in the Shopware Store, this process often comes naturally, as you need to update your code to the latest Shopware version.
For extensions published in the Shopware Store, refer to the extended Quality Guidelines in the official Shopware documentation.
In this learning unit, you have learned:
With these best practices, you can ensure that your Shopware projects remain stable, maintainable, and ready for future growth.
Inspect discussions, ask questions and give feedback.
Sign up now