When creating a new plugin, custom fields must often be used. You can read about their numerous functionalities on the Shopware documentation page. In a nutshell, Shopware’s custom fields are used to store additional data on entities. They are stored in the database in JSON formations loaded as an array. In this article, we will focus on how they should be properly implemented in a project. The text is aimed at practitioners who have already had their first experiences with adding plugins.
The most frequent mistakes
1. The most common error is that when installing a plugin, a custom fields definition is added which is not removed when they are no longer needed. As a result, the plug-in will show an error that such a definition already exists, when reinstalling.
2. Another problem that may be encountered is too much data. Sometimes some definitions need to be added. Then our main plug-in installation file will be really long if there are more functionalities. In this case, we advise to move the installation of custom fields to a separate class, and even to several ones, if you have a lot of custom fields.
Below is an example of the main plugin file.
<?php declare(strict_types=1);
namespace Example;
use Example\Service\OrderCustomFields;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\ActivateContext;
use Shopware\Core\Framework\Plugin\Context\DeactivateContext;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
class Example extends Plugin
{
public function install(InstallContext $installContext): void
{
(new OrderCustomFields($this--->container))
->installCustomFields($installContext->getContext());
}
public function activate(ActivateContext $activateContext): void
{
(new OrderCustomFields($this->container))
->activateCustomFields($activateContext->getContext());
}
public function deactivate(DeactivateContext $deactivateContext): void
{
(new OrderCustomFields($this->container))
->deactivateCustomFields($deactivateContext->getContext());
}
public function uninstall(UninstallContext $uninstallContext): void
{
(new OrderCustomFields($this->container))
->uninstallCustomFields($uninstallContext->getContext());
}
}
Such a solution will ensure easier expansion of additional fields in the future and will prevent excessive amount of code in the main class of the plugin.
How to install Order Custom Field
Moving on to the OrderCustomField class itself. We suggest to define keys of the custom fields as const at the beginning of the class definition to facilitate their handling later.
class OrderCustomFields
{
public const LABEL_CUSTOM_FIELD_KEY = 'custom_label';
public const ONE_CUSTOM_FIELD_KEY = 'custom_field_one';
public const TWO_CUSTOM_FIELD_KEY = 'custom_field_two';
//...
The first key allows us to identify the entire set of custom fields, while the remaining ones relate to the identification of the fields themselves. The class without creating the fields themselves – we’ll get to that later – will look like the one below. However, it must be remembered that class is one definition of fields. If you want to put several custom fields definitions in one class, you have to include it in the functions to be activated, delete functions.
class OrderCustomFields
{
public const LABEL_CUSTOM_FIELD_KEY = custom_label;
public const ONE_CUSTOM_FIELD_KEY = 'custom_field_one';
public const TWO_CUSTOM_FIELD_KEY = 'custom_field_two';
protected ContainerInterface $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function installCustomFields(Context $context): void
{
$searchResults = $this->getCustomFields($context);
if($searchResults->getTotal() === 0)
{
// Add custom fields
}
}
public function activateCustomFields(Context $context): void
{
$this->setCustomFieldsActivate(
$this->getCustomFields($context)->getIds(), true, $context);
}
public function deactivateCustomFields(Context $context): void
{
$this->setCustomFieldsActivate(
$this->getCustomFields($context)->getIds(), false, $context);
}
public function uninstallCustomFields(Context $context): void
{
$customFieldsIds = $this->getCustomFields($context)->getIds();
if(count($customFieldsIds) !== 0)
{
$this->container->get('custom_field_set.repository')->delete([[
'id' => $customFieldsIds[0]
]], $context);
}
}
private function setCustomFieldsActivate(array $customFieldsIds, bool $active, Context $context): void
{
$this->container->get('custom_field_set.repository')->update([[
'id' => $customFieldsIds[0],
'active' => $active
]], $context);
}
private function getCustomFields(Context $context): IdSearchResult
{
return $this->container->get('custom_field_set.repository')->searchIds(
(new Criteria())->addFilter(new EqualsFilter(
'name', self::LABEL_CUSTOM_FIELD_KEY)), $context);
}
}
Creating custom fields
If you have already created the flow custom fields management structure, you can move on for the very creation of fields. When adding custom fields, remember that the definition of the custom fields set (a table in the database named “custom_field_set”) and the definition of the fields themselves (“custom_field” tables) must be added. Changing the set statute affects all related fields.
For example, the simplest field structure created looks like this:
public function installCustomFields(Context $context): void
{
$searchResults = $this->getCustomFields($context);
if($searchResults->getTotal() === 0)
{
$customField = [
'name' => self::LABEL_CUSTOM_FIELD_KEY,
'active' => false,
'config' => [
'label' => [
'en-GB' => 'Label translation'
]
],
'relations' => [
[
'entityName' => 'order'
]
],
'customFields' => [
[
'name' => self::ONE_CUSTOM_FIELD_KEY,
'type' => CustomFieldTypes::TEXT,
'config' => [
'label' => [
'en-GB' => 'Field one translation'
]
]
], [
'name' => self::TWO_CUSTOM_FIELD_KEY,
'type' => CustomFieldTypes::TEXT,
'config' => [
'label' => [
'en-GB' => 'Field two translation'
]
]
]
]
];
$this->container->get('custom_field_set.repository')->create([$customField], $context);
}
}
And that is where we could end. When creating custom fields, it is usually added in one language, in which the customer will work. When creating a plug-in for the store, a translation in several languages must be introduced. How to do it in the best way? The following example not only separates the translation from the code for adding fields, but also adds to the database only those translations whose language is added to the Shopware database.
The first step is to add a new class containing translations:
class CustomFieldsTranslation
{
public const TRANSLATIONS = [
'en-GB' => [
OrderCustomFields::LABEL_CUSTOM_FIELD_KEY => 'Label translation',
OrderCustomFields::ONE_CUSTOM_FIELD_KEY => 'Field one translation',
OrderCustomFields::TWO_CUSTOM_FIELD_KEY => 'Field two translation'
],
'pl-PL' => [
OrderCustomFields::LABEL_CUSTOM_FIELD_KEY => 'Label translation',
OrderCustomFields::ONE_CUSTOM_FIELD_KEY => 'Field one translation',
OrderCustomFields::TWO_CUSTOM_FIELD_KEY => 'Field two translation'
]
];
}
Coming back to the class that creates custom fields. A function that downloads languages is added:
private function getAvailableLanguages(Context $context): array
{
return $this->container->get('language.repository')->search(
(new Criteria())->addAssociation('locale'),
$context
)->getElements();
}
And a function that will generate translations is added:
private function generateTranslation(string $key, array $languages): array
{
$translation = [];
foreach($languages as $language)
{
$code = $language->getLocale()->getCode();
if(isset(CustomFieldsTranslation::TRANSLATIONS[$code]))
{
$translation['config']['label'][$code] = CustomFieldsTranslation::TRANSLATIONS[$code][$key];
}
}
return $translation;
}
And then corrections to the generator function are made:
if($searchResults->getTotal() === 0)
{
$languages = $this->getAvailableLanguages($context);
$customField = [
'name' => self::LABEL_CUSTOM_FIELD_KEY,
'active' => false,
'config' => $this->generateTranslation(self::LABEL_CUSTOM_FIELD_KEY, $languages),
'customFields' => [
[
'name' => self::ONE_CUSTOM_FIELD_KEY,
'type' => CustomFieldTypes::TEXT,
'config' => $this->generateTranslation(self::ONE_CUSTOM_FIELD_KEY, $languages)
], [
'name' => self::TWO_CUSTOM_FIELD_KEY,
'type' => CustomFieldTypes::TEXT,
'config' => $this->generateTranslation(self::TWO_CUSTOM_FIELD_KEY, $languages)
]
]
];
$this->container->get('custom_field_set.repository')->create([$customField], $context);
}
In this way, the most comprehensive implementation of custom fields occurs, which can be
easily expanded by adding new fields or languages.