Appearance
Conventions & workflow
For developers
Conventions shared by every KommandHub plugin repository — for contributors and for developers reading our code. Everything specific to one plugin (architecture, events, APIs, data model, testing) is in that plugin's For developers section under Plugins. For general Shopware plugin development see the Shopware developer documentation.
Layout: feature-first modules
Code under src/ is grouped by feature, following the layout of Shopware's own plugins (for example SwagPayPal). A top-level directory is a boundary — Checkout/, Webhook/, BankVerification/, Notification/ — and inside it classes sit in flat, Symfony-style folders: Service, Subscriber, Handler, Struct, Event, Enum, Controller.
- Shopware entry points (payment handlers, controllers, subscribers, Flow actions) stay thin and delegate to services.
- A module reaches another module through a service, never by reaching into its internals.
- Every external API call goes through a typed client class (for the payment plugins, one class per endpoint under
Client/Resource/), never ad-hoc HTTP.
Some plugins add stricter layering on top (for example a framework-agnostic domain core); each plugin's developer documentation describes its own architecture.
Shared building blocks
| Piece | Where | What it does |
|---|---|---|
Setting\Service\Config | every plugin | Typed reader over Shopware's SystemConfigService for the plugin's own domain (<PluginName>.config.<key>), always sales-channel aware. |
Logging\ConfigurableLogger | every plugin except Click and Pick | PSR-3 wrapper that honours the debug logging and log levels settings per sales channel. error and above always pass. |
Installer\* | payment plugins, Click and Pick, Demo Data | Idempotent installers for payment/shipping methods and custom fields, run on install and update. |
Migration\* | plugins with their own tables or states | Append-only; never edit a released migration. |
Pass the sales channel to the logger so the right scope is used:
php
$this->logger->info('Something happened', [
ConfigurableLogger::CONTEXT_SALES_CHANNEL_ID => $salesChannelId,
]);Rules that apply everywhere
- Money at gateway boundaries goes through the plugin's currency helper. Paystack wants minor units (kobo); Flutterwave wants major units. Zero-decimal (XOF, RWF, UGX) and three-decimal (KWD) currencies are in scope. Never multiply by 100 inline.
- Webhooks are an attack surface. Signatures are compared with
hash_equals, missing secrets fail closed, and state changes are re-verified against the provider API where the provider does not sign the payload. - Background messages carry IDs and scalars, not entities, and their handlers are idempotent — delivery is at least once.
- Custom-field keys live in one constants class per plugin (
PaystackConstants,FlutterwaveConstants,DemoDataConstants), because they are lookup keys for stored data. - Interfaces are not auto-aliased. Services are autowired through a
../../*glob inservices.yml; a constructor-injected*Interfaceneeds an explicitalias:entry. - Twig blocks a plugin adds are namespaced (
{% block kommandhub_sms_… %}) so two plugins extending the same template do not collide.
Quality gates
Each repository runs the same gate locally and in GitHub Actions:
bash
make cs-fix && make analyse && make test- PHPStan at level 9.
- php-cs-fixer (PSR-12 plus project rules).
- PHPUnit 11.
tests/Unitmirrorssrc/and needs no kernel; tests that boot Shopware carry#[Group('kernel')]and are excluded in CI. - CI enforces 100 % line coverage (what counts towards it is set per plugin in
phpunit.dist.xml).
Built assets
Administration (Vue, Vite) and storefront JavaScript builds are committed under src/Resources/public/ and src/Resources/app/storefront/dist/. They are generated — change the source under src/Resources/app/**/src/ and rebuild; never hand-edit the output.
Branching and releases
- Branch from
develop, open pull requests againstdevelop, mergedevelopintomainto release. - Conventional Commits (
feat:,fix:,refactor:,test:…). - A release bumps
versionincomposer.json, adds a# <version>section toCHANGELOG.md(plusCHANGELOG_de-DE.md/CHANGELOG_fr-FR.mdwhere present), passesmake validate-plugin(Shopware Store compliance), and is tagged.