it-bens / shopware-code-based-plugin-configuration
Package to provide a code based and typed way to create Shopware configurations besides XML files.
Installs: 169
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
Type:package
Requires
- php: ^8.1
- shopware/core: ^6.5
Requires (Dev)
- captainhook/captainhook: ^5.23
- captainhook/plugin-composer: ^5.3
- frosh/shopware-rector: ^0.3.0
- phpstan/phpstan: ^1.10
- phpstan/phpstan-phpunit: ^1.3
- phpstan/phpstan-symfony: ^1.3
- phpunit/phpunit: ^10.5
- rector/rector: ^1.0
- symplify/coding-standard: ^12.0
- symplify/easy-coding-standard: ^12.1
This package is auto-updated.
Last update: 2024-10-28 00:16:54 UTC
README
Shopware provides an easy way to create a plugin configuration without any implementing any frontend code: https://developer.shopware.com/docs/guides/plugins/plugins/plugin-fundamentals/add-plugin-configuration.html
While the underlying system for the internal configuration generation is very flexible, the config.xml
file is the only way to leverage this system. This package aims to provide another method: code-based configuration generation.
Is this a Shopware Plugin?
No. While this package is tied to Shopware, it won't react on any events on it's own. It is meant to be used by a Shopware plugin.
How can add this package in a Shopware plugin?
First, the package has to be installed via composer:
composer require it-bens/shopware-code-based-plugin-configuration
This can be done in the plugin's composer.json
file. But remember to enable composer in the plugin class.
public function executeComposerCommands(): bool { return true; }
This package provides two Symfony compiler passes. The ConfigurationCardConfigReaderPass
will add all the services necessary for the configuration generation to the service container. It can be added to the plugin's build
method.
use ITB\ShopwareCodeBasedPluginConfiguration\DependencyInjection\ConfigurationCardConfigReaderPass; public function build(ContainerBuilder $container): void { // ... parent::build($container); $container->addCompilerPass(new ConfigurationCardConfigReaderPass()); }
Why are the default configuration values missing?
While the mentioned compiler pass is sufficient to generate the plugin configuration at runtime, a convenient feature is missing: default values. The ConfigurationCardConfigSaverPass
is required to "hack" into the Shopware plugin configuration persistence.
use ITB\ShopwareCodeBasedPluginConfiguration\DependencyInjection\ConfigurationCardConfigReaderPass; use ITB\ShopwareCodeBasedPluginConfiguration\DependencyInjection\ConfigurationCardConfigSaverPass; public function build(ContainerBuilder $container): void { // ... parent::build($container); $container->addCompilerPass(new ConfigurationCardConfigReaderPass()); $container->addCompilerPass(new ConfigurationCardConfigSaverPass()); }
For detailed information why this is necessary and why the usage is "hacky" look at How does the package work? - Shopware plugin configuration persistence.
How can I use this package?
Coding the configuration into cards
This package provides classes to define configuration cards (as seen in the Shopware plugin administration).
A ConfigurationCard
has a title in english, a title in german and a list of ConfigurationInputField
objects. The supported ConfigurationInputField
implementations are:
BoolField
IntegerField
SingleSelectField
TextField
Some of these implementations differ in their required information. But because they mostly require the same information, all require a GeneralFieldInputInformation
object. Here is an example for a configuration card with all types of input fields:
use ITB\ShopwareCodeBasedPluginConfiguration\ConfigurationCard; use ITB\ShopwareCodeBasedPluginConfiguration\ConfigurationInputField\BoolField; use ITB\ShopwareCodeBasedPluginConfiguration\ConfigurationInputField\IntegerField; use ITB\ShopwareCodeBasedPluginConfiguration\ConfigurationInputField\SingleSelectField; use ITB\ShopwareCodeBasedPluginConfiguration\ConfigurationInputField\SingleSelectFieldOption; use ITB\ShopwareCodeBasedPluginConfiguration\ConfigurationInputField\TextField; use ITB\ShopwareCodeBasedPluginConfiguration\GeneralFieldInputInformation; $enabledFieldInformation = new GeneralFieldInputInformation( name: 'enabled', labelInEnglish: 'Enabled', labelInGerman: 'Aktiviert', helpTextInEnglish: 'Enable or disable the feature', helpTextInGerman: 'Aktiviere oder deaktiviere das Feature' ); $enabledField = new BoolField(new GeneralFieldInformation(generalInformation: $enabledFieldInformation, defaultValue: false); $integerFieldInformation = new GeneralFieldInputInformation( name: 'position', labelInEnglish: 'Position', labelInGerman: 'Position', helpTextInEnglish: 'The position in the list', helpTextInGerman: 'Die Position in der Liste' ); $integerField = new IntegerField(new GeneralFieldInformation(generalInformation: $integerFieldInformation, defaultValue: 0); $selectFieldInformation = new GeneralFieldInputInformation( name: 'color', labelInEnglish: 'Color', labelInGerman: 'Farbe', helpTextInEnglish: 'The color of the item', helpTextInGerman: 'Die Farbe des Elements' ); $selectField = new SingleSelectField( new GeneralFieldInformation(generalInformation: $selectFieldInformation, defaultValue: 'red'), [ new SingleSelectFieldOption(id: 'red', nameInEnglish: 'Red', nameInGerman: 'Rot'), new SingleSelectFieldOption(id: 'blue', nameInEnglish: 'Blue', nameInGerman: 'Blau'), new SingleSelectFieldOption(id: 'green', nameInEnglish: 'Green', nameInGerman: 'Grün'), ] ); $textFieldInformation = new GeneralFieldInputInformation( name: 'description', labelInEnglish: 'Description', labelInGerman: 'Beschreibung', helpTextInEnglish: 'A description of the item', helpTextInGerman: 'Eine Beschreibung des Elements' ); $textField = new TextField(new GeneralFieldInformation(generalInformation: $textFieldInformation, defaultValue: ''); $configurationCard = new ConfigurationCard( titleInEnglish: 'General Settings', titleInGerman: 'Allgemeine Einstellungen', inputFields: [$enabledField, $integerField, $selectField, $textField] );
Injecting the cards into the configuration
The package uses services that implement the ConfigurationCardProvider
interface. You can create as many implementations in service instances as you like. Because Shopware uses the same loading mechanism for all plugins, the scope of the ConfigurationCardProvider
has to be declared. This is done in the getBundleClasses
method of the plugin class.
/** * @return class-string<Bundle>[] */ public function getBundleClasses(): array { return [ YourPluginClass::class ]; }
The getPriority
method is used to sort the providers if more than one provider is registered for the same plugin.
public function getPriority(): int { return 100; }
And finally the getConfigurationCards
method is used to return the configuration cards.
/** * @return ConfigurationCard[] */ public function getConfigurationCards(): array { return $this->configurationCards; }
How does the package work?
The package decorates the Shopware\Core\System\SystemConfig\Util\ConfigReader
service from Shopware. This service is used to read the config.xml
file of a plugin. It decodes the XML file into an array. This package keeps the XML-based configuration of plugin and extends it with the data from matching configuration card providers.
The decoration is done via compiler pass in three steps:
- all services that implement the
ConfigurationCardProvider
interface are collected and tagged. - a
ConfigurationCardProviderProvider
definition is created that receives all services from (1). - the
ConfigurationCardConfigReader
definition is created with decoration of theShopware\Core\System\SystemConfig\Util\ConfigReader
service and theConfigurationCardProviderProvider
is injected into theConfigurationCardConfigReader
. - the
ConfigurationCardConfigReader
is set as an alias for theShopware\Core\System\SystemConfig\Util\ConfigReader
service to replace it.
Shopware plugin configuration persistence
Shopware saves plugin configurations in it's database as key-value pairs. When a plugin is installed, Shopware reads the configuration with the Shopware\Core\System\SystemConfig\Util\ConfigReader
and persists the default values. However, the services of a plugin are added to the DI container during the plugin activation and not the installation. This means that the service decoration that allows the code-based configuration generation is not used during the installation. Only the default values from the config.xml
file are persisted as a result.
This package provides a service subscriber that subscribes to the Shopware PluginPostActivateEvent
and the PluginPostUpdateEvent
. The service requires the plugin/bundle instance to perform the configuration persistence. The subscriber checks if the configuration persistence is necessary by using the plugin base class from the events the bundle classes defined in the registered ConfigurationCardProvider
instances and fetches the plugin instance from the KernelPluginCollection
service. The compiler pass takes care of the service definition, argument injection and the subscription tagging.
Shopware checks for already persisted configuration values and does not overwrite them. So multiple installations/deinstallations/activations/deactivations of a plugin that uses this package will not reset the configuration values unless the "remove all data" option is used.
The HTTP cache might be reset after the plugin activation. To avoid issues with the newly persisted configuration values, the event subscriber is registered with a priority of 100 to be executed as early as possible (at least earlier before other event subscribers with priority 0).
Contributing
I am really happy that the software developer community loves Open Source, like I do! ♥
That's why I appreciate every issue that is opened (preferably constructive) and every pull request that provides other or even better code to this package.
You are all breathtaking!