diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 404b1218..9a5cad42 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -63,6 +63,8 @@ jobs:
- run: make install
+ - run: make sylius.fixtures.local
+
- run: make test.composer
- run: make test.phpcs
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index ea3ecf0b..29f25c04 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -49,10 +49,8 @@
'binary_operator_spaces' => true,
'blank_line_after_opening_tag' => true,
'blank_line_after_namespace' => true,
+ 'blank_lines_before_namespace' => true,
'blank_line_before_statement' => true,
- 'braces' => [
- 'allow_single_line_closure' => true,
- ],
'cast_spaces' => true,
'class_attributes_separation' => true,
'class_definition' => [
@@ -88,7 +86,6 @@
'fully_qualified_strict_types' => true,
'function_declaration' => true,
'function_to_constant' => true,
- 'function_typehint_space' => true,
'general_phpdoc_tag_rename' => true,
'global_namespace_import' => [
'import_classes' => true,
@@ -115,6 +112,7 @@
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'method_argument_space' => true,
+ 'modernize_strpos' => false,
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => [
@@ -181,6 +179,7 @@
'no_whitespace_in_blank_line' => true,
'non_printable_character' => true,
'normalize_index_brace' => true,
+ 'nullable_type_declaration_for_default_null_value' => false,
'object_operator_without_whitespace' => true,
'ordered_imports' => [
'imports_order' => [
@@ -215,7 +214,9 @@
'phpdoc_order' => true,
'phpdoc_return_self_reference' => true,
'phpdoc_scalar' => true,
- 'phpdoc_separation' => true,
+ 'phpdoc_separation' => ['groups' => [
+ ['ORM\\*'], ['Assert\\*'],
+ ]],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_tag_type' => true,
'phpdoc_to_comment' => false,
@@ -235,7 +236,6 @@
'self_accessor' => true,
'short_scalar_cast' => true,
'single_blank_line_at_eof' => true,
- 'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => true,
'single_import_per_statement' => true,
'single_line_after_imports' => true,
@@ -252,6 +252,7 @@
'elements' => ['arrays'],
],
'trim_array_spaces' => true,
+ 'type_declaration_spaces' => true,
'unary_operator_spaces' => true,
'visibility_required' => [
'elements' => [
diff --git a/Makefile b/Makefile
index 39cdb0df..08c0b8ac 100644
--- a/Makefile
+++ b/Makefile
@@ -142,7 +142,7 @@ test.twig: ## Validate Twig templates
### SYLIUS
### ¯¯¯¯¯¯
-sylius: dependencies sylius.database sylius.fixtures sylius.assets ## Install Sylius
+sylius: dependencies sylius.database sylius.fixtures sylius.assets messenger.setup ## Install Sylius
.PHONY: sylius
sylius.database: ## Setup the database
@@ -153,11 +153,17 @@ sylius.database: ## Setup the database
sylius.fixtures: ## Run the fixtures
${CONSOLE} sylius:fixtures:load -n default
+sylius.fixtures.local: ## Run the local fixtures (for testing purpose)
+ ${CONSOLE} sylius:fixtures:load -n local
+
sylius.assets: ## Install all assets with symlinks
${CONSOLE} assets:install --symlink
${CONSOLE} sylius:install:assets
${CONSOLE} sylius:theme:assets:install --symlink
+messenger.setup: ## Setup Messenger transports
+ ${CONSOLE} messenger:setup-transports
+
###
### PLATFORM
### ¯¯¯¯¯¯¯¯
diff --git a/README.md b/README.md
index d1e11aca..4ca716c3 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@
Settings for Sylius
[![Settings Plugin license](https://img.shields.io/github/license/monsieurbiz/SyliusSettingsPlugin?public)](https://github.com/monsieurbiz/SyliusSettingsPlugin/blob/master/LICENSE.txt)
-[![Tests Status](https://img.shields.io/github/workflow/status/monsieurbiz/SyliusSettingsPlugin/Tests?logo=github)](https://github.com/monsieurbiz/SyliusSettingsPlugin/actions?query=workflow%3ATests)
-[![Security Status](https://img.shields.io/github/workflow/status/monsieurbiz/SyliusSettingsPlugin/Security?label=security&logo=github)](https://github.com/monsieurbiz/SyliusSettingsPlugin/actions?query=workflow%3ASecurity)
+[![Tests](https://github.com/monsieurbiz/SyliusSettingsPlugin/actions/workflows/tests.yaml/badge.svg?branch=master&event=push)](https://github.com/monsieurbiz/SyliusSettingsPlugin/actions/workflows/tests.yaml)
+[![Security](https://github.com/monsieurbiz/SyliusSettingsPlugin/actions/workflows/security.yaml/badge.svg?branch=master&event=push)](https://github.com/monsieurbiz/SyliusSettingsPlugin/actions/workflows/security.yaml)
This plugin gives you the ability to have Plugins oriented settings in your favorite e-commerce platform, Sylius.
@@ -101,6 +101,38 @@ use MonsieurBiz\SyliusSettingsPlugin\Provider\SettingsProviderInterface;
}
```
+### Use fixtures
+
+We've implemented a fixtures loader to help you to create your settings if you need to have different settings for your
+tests or project (by channel, by locale…).
+
+You need to create a yaml file with your fixtures, like explained in the documentation of Sylius.
+You can find our own example in the source code, section `sylius_fixtures`: [configuration file](dist/config/packages/monsieurbiz_settings_plugin_custom.yaml).
+
+It's also possible to run test fixtures with a local suite in development: `make sylius.fixtures.local`.
+
+By default, a fixture will replace the value of a setting if it already exists.
+If you want to keep a value as it is in the database when running this fixture, you can use the flag `ignore_if_exists: true` for each element that you want to be kept.
+
+### Use CLI
+
+You can use a CLI command to set a value for a setting directly from the console:
+`$ ./bin/console monsieurbiz:settings:set {alias} {path} {value} --channel="FASHION_WEB" --locale="en_US" --type="text"`
+
+
+Examples:
+```bash
+$ ./bin/console monsieurbiz:settings:set app.default demo_message 'fashion message' --channel="FASHION_WEB" --locale="en_US"
+$ ./bin/console monsieurbiz:settings:set app.default demo_json '{"foo":"baz"}' --channel="FASHION_WEB" --locale="en_US" --type="json"
+$ ./bin/console monsieurbiz:settings:set app.default demo_datetime '2023-07-24 01:02:03' --channel="FASHION_WEB" --locale="en_US" --type="datetime"
+$ ./bin/console monsieurbiz:settings:set app.default enabled 0
+```
+The options channel and locale can be omitted if you want to set the value for a global scope.
+If a value exists for the given scope the type can be omitted as it will be the same as the existing one unless you want to change the type.
+For a new value you need to specify the type.
+
+⚠️ When specifying the type, be sure to know what you are doing as it should be coherent with the Form Type of the field.
+
## Contributing
You can find a way to run the plugin without effort in the file [DEVELOPMENT.md](./DEVELOPMENT.md).
diff --git a/composer.json b/composer.json
index 23e951dc..db416cc8 100644
--- a/composer.json
+++ b/composer.json
@@ -63,6 +63,9 @@
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
+ },
+ "symfony": {
+ "allow-contrib": "true"
}
},
"config": {
@@ -70,7 +73,8 @@
"dealerdirect/phpcodesniffer-composer-installer": true,
"symfony/thanks": true,
"ergebnis/composer-normalize": true,
- "symfony/flex": true
+ "symfony/flex": true,
+ "php-http/discovery": true
}
}
}
diff --git a/dist/config/packages/monsieurbiz_settings_plugin_custom.yaml b/dist/config/packages/monsieurbiz_settings_plugin_custom.yaml
index 8921c605..e3f0158e 100644
--- a/dist/config/packages/monsieurbiz_settings_plugin_custom.yaml
+++ b/dist/config/packages/monsieurbiz_settings_plugin_custom.yaml
@@ -19,3 +19,83 @@ sylius_ui:
blocks:
demo_message:
template: '/views/message.html.twig'
+
+sylius_fixtures:
+ suites:
+ default:
+ fixtures:
+ monsieurbiz_settings:
+ options:
+ custom:
+ -
+ alias: app.default
+ path: demo_message
+ channel: FASHION_WEB
+ locale: en_US
+ type: text
+ value: My amazing very fashion message
+ -
+ alias: app.default
+ path: demo_message
+ # no channel because we want the default
+ locale: fr_FR
+ type: text
+ value: Mon message vraiment très fashion
+ -
+ alias: app.default
+ path: demo_message
+ # no channel because we want the default
+ # no locale because we want the default
+ type: text
+ value: My very default message
+
+ local:
+ listeners:
+ logger: ~
+ fixtures:
+ monsieurbiz_settings:
+ options:
+ custom:
+ -
+ alias: app.default
+ path: test_bool
+ type: boolean
+ value: true
+ ignore_if_exists: true
+ -
+ alias: app.default
+ path: test_bool2
+ type: boolean
+ value: false
+ -
+ alias: app.default
+ path: test_json
+ type: json
+ value: |
+ {"foo":"bar"}
+ -
+ alias: app.default
+ path: test_json_array
+ type: json
+ value: {"foo":"baz"}
+ -
+ alias: app.default
+ path: test_int
+ type: integer
+ value: 42
+ -
+ alias: app.default
+ path: test_float
+ type: float
+ value: 13.37
+ -
+ alias: app.default
+ path: test_date
+ type: date
+ value: 2023-07-24
+ -
+ alias: app.default
+ path: test_datetime
+ type: datetime
+ value: 2023-07-24 01:02:03
+
diff --git a/src/Command/SetSettingsCommand.php b/src/Command/SetSettingsCommand.php
new file mode 100644
index 00000000..0edd9760
--- /dev/null
+++ b/src/Command/SetSettingsCommand.php
@@ -0,0 +1,137 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Command;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Exception;
+use MonsieurBiz\SyliusSettingsPlugin\Exception\SettingsException;
+use MonsieurBiz\SyliusSettingsPlugin\Formatter\SettingsFormatterInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Provider\SettingProviderInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Settings\RegistryInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Settings\SettingsInterface;
+use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
+use Sylius\Component\Core\Model\ChannelInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SetSettingsCommand extends Command
+{
+ private const ARGUMENT_ALIAS = 'alias';
+
+ private const ARGUMENT_PATH = 'path';
+
+ private const OPTION_CHANNEL = 'channel';
+
+ private const OPTION_LOCALE = 'locale';
+
+ private const OPTION_TYPE = 'type';
+
+ private const ARGUMENT_VALUE = 'value';
+
+ private RegistryInterface $settingsRegistry;
+
+ private ChannelRepositoryInterface $channelRepository;
+
+ private EntityManagerInterface $settingManager;
+
+ private SettingsFormatterInterface $settingsFormatter;
+
+ private SettingProviderInterface $settingProvider;
+
+ protected static $defaultName = 'monsieurbiz:settings:set';
+
+ public function __construct(
+ RegistryInterface $settingsRegistry,
+ ChannelRepositoryInterface $channelRepository,
+ EntityManagerInterface $settingManager,
+ SettingsFormatterInterface $settingsFormatter,
+ SettingProviderInterface $settingProvider,
+ string $name = null
+ ) {
+ $this->settingsRegistry = $settingsRegistry;
+ $this->channelRepository = $channelRepository;
+ $this->settingManager = $settingManager;
+ $this->settingsFormatter = $settingsFormatter;
+ $this->settingProvider = $settingProvider;
+ parent::__construct($name);
+ }
+
+ protected function configure(): void
+ {
+ $this
+ ->setDescription('Set a settings value for a given path')
+ ->setHelp('This command allows you to set a settings value for a given path')
+ ->addArgument(self::ARGUMENT_ALIAS, InputArgument::REQUIRED, 'Alias of the settings like {vendor}.{plugin} from the setting definition')
+ ->addArgument(self::ARGUMENT_PATH, InputArgument::REQUIRED, 'Path of the settings')
+ ->addArgument(self::ARGUMENT_VALUE, InputArgument::REQUIRED, 'Value of the settings')
+ ->addOption(self::OPTION_TYPE, 't', InputOption::VALUE_OPTIONAL, 'Type of the settings', null)
+ ->addOption(self::OPTION_CHANNEL, 'c', InputOption::VALUE_OPTIONAL, 'Channel code')
+ ->addOption(self::OPTION_LOCALE, 'l', InputOption::VALUE_OPTIONAL, 'Locale code')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ /** @var string $alias */
+ $alias = $input->getArgument(self::ARGUMENT_ALIAS);
+ /** @var string $path */
+ $path = $input->getArgument(self::ARGUMENT_PATH);
+ $channelCode = $input->getOption(self::OPTION_CHANNEL);
+ /** @var ?string $locale */
+ $locale = $input->getOption(self::OPTION_LOCALE);
+
+ $channel = null;
+ if (null !== $channelCode) {
+ /** @var ?ChannelInterface $channel */
+ $channel = $this->channelRepository->findOneBy(['code' => $channelCode]);
+ }
+
+ /** @var ?SettingsInterface $settings */
+ $settings = $this->settingsRegistry->getByAlias($alias);
+
+ if (null === $settings) {
+ throw new SettingsException(sprintf('The alias "%s" is not valid.', $alias));
+ }
+
+ ['vendor' => $vendor, 'plugin' => $plugin] = $settings->getAliasAsArray();
+ $setting = $this->settingProvider->getSettingOrCreateNew($vendor, $plugin, $path, $locale, $channel);
+
+ /** @var string $type */
+ $type = $input->getOption(self::OPTION_TYPE) ?? $setting->getStorageType();
+ $this->settingProvider->validateType($type);
+
+ $value = $input->getArgument(self::ARGUMENT_VALUE);
+
+ $this->settingProvider->resetExistingValue($setting);
+ $setting->setStorageType($type);
+ /** @phpstan-ignore-next-line */
+ $setting->setValue($this->settingsFormatter->formatValue($type, $value));
+
+ $this->settingManager->persist($setting);
+ $this->settingManager->flush();
+ } catch (Exception $e) {
+ $output->writeln(sprintf('%s', $e->getMessage()));
+
+ return Command::FAILURE;
+ }
+
+ $output->writeln(sprintf('%s', 'The setting has been saved'));
+
+ return Command::SUCCESS;
+ }
+}
diff --git a/src/DependencyInjection/MonsieurBizSyliusSettingsExtension.php b/src/DependencyInjection/MonsieurBizSyliusSettingsExtension.php
index 03132ca1..ca7ac1a0 100644
--- a/src/DependencyInjection/MonsieurBizSyliusSettingsExtension.php
+++ b/src/DependencyInjection/MonsieurBizSyliusSettingsExtension.php
@@ -47,8 +47,8 @@ public function getAlias(): string
public function prepend(ContainerBuilder $container): void
{
if (
- $container->hasParameter('sylius_core.prepend_doctrine_migrations') &&
- !$container->getParameter('sylius_core.prepend_doctrine_migrations')
+ $container->hasParameter('sylius_core.prepend_doctrine_migrations')
+ && !$container->getParameter('sylius_core.prepend_doctrine_migrations')
) {
return;
}
diff --git a/src/Entity/Setting/Setting.php b/src/Entity/Setting/Setting.php
index 734ffd3b..fb389850 100644
--- a/src/Entity/Setting/Setting.php
+++ b/src/Entity/Setting/Setting.php
@@ -55,6 +55,7 @@ class Setting implements SettingInterface
/**
* @ORM\ManyToOne(targetEntity="\Sylius\Component\Core\Model\ChannelInterface")
* @ORM\JoinColumn(name="channel_id", referencedColumnName="id")
+ *
* @Assert\Type(type="\Sylius\Component\Core\Model\ChannelInterface")
*/
private ?ChannelInterface $channel;
@@ -67,7 +68,7 @@ class Setting implements SettingInterface
/**
* @ORM\Column(name="storage_type", type="string", length=10, nullable=false)
*/
- private ?string $storageType;
+ private ?string $storageType = null;
/**
* @ORM\Column(name="text_value", type="text", length=65535, nullable=true)
@@ -108,6 +109,7 @@ class Setting implements SettingInterface
* @var DateTimeInterface|null
*
* @ORM\Column(name="created_at", type="datetime_immutable")
+ *
* @Gedmo\Timestampable(on="create")
*/
protected $createdAt;
@@ -116,6 +118,7 @@ class Setting implements SettingInterface
* @var DateTimeInterface|null
*
* @ORM\Column(name="updated_at", type="datetime")
+ *
* @Gedmo\Timestampable(on="update")
*/
protected $updatedAt;
@@ -332,4 +335,17 @@ public function setJsonValue(?array $jsonValue): void
{
$this->jsonValue = $jsonValue;
}
+
+ public static function getAllStorageTypes(): array
+ {
+ return [
+ SettingInterface::STORAGE_TYPE_TEXT,
+ SettingInterface::STORAGE_TYPE_BOOLEAN,
+ SettingInterface::STORAGE_TYPE_INTEGER,
+ SettingInterface::STORAGE_TYPE_FLOAT,
+ SettingInterface::STORAGE_TYPE_JSON,
+ SettingInterface::STORAGE_TYPE_DATE,
+ SettingInterface::STORAGE_TYPE_DATETIME,
+ ];
+ }
}
diff --git a/src/Entity/Setting/SettingInterface.php b/src/Entity/Setting/SettingInterface.php
index 8cda77d5..5cbfe450 100644
--- a/src/Entity/Setting/SettingInterface.php
+++ b/src/Entity/Setting/SettingInterface.php
@@ -101,4 +101,6 @@ public function setDateValue(?DateTimeInterface $dateValue): void;
public function getJsonValue(): ?array;
public function setJsonValue(?array $jsonValue): void;
+
+ public static function getAllStorageTypes(): array;
}
diff --git a/src/Fixture/Factory/SettingsFixtureFactory.php b/src/Fixture/Factory/SettingsFixtureFactory.php
new file mode 100644
index 00000000..4008e675
--- /dev/null
+++ b/src/Fixture/Factory/SettingsFixtureFactory.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Fixture\Factory;
+
+use MonsieurBiz\SyliusSettingsPlugin\Entity\Setting\Setting;
+use MonsieurBiz\SyliusSettingsPlugin\Entity\Setting\SettingInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Formatter\SettingsFormatterInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Provider\SettingProviderInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Settings\RegistryInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Settings\SettingsInterface;
+use Sylius\Bundle\CoreBundle\Fixture\Factory\AbstractExampleFactory;
+use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
+use Sylius\Component\Core\Model\ChannelInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+class SettingsFixtureFactory extends AbstractExampleFactory
+{
+ private RegistryInterface $settingsRegistry;
+
+ private OptionsResolver $optionsResolver;
+
+ private ChannelRepositoryInterface $channelRepository;
+
+ private SettingsFormatterInterface $settingsFormatter;
+
+ private SettingProviderInterface $settingProvider;
+
+ public function __construct(
+ RegistryInterface $settingsRegistry,
+ ChannelRepositoryInterface $channelRepository,
+ SettingsFormatterInterface $settingsFormatter,
+ SettingProviderInterface $settingProvider
+ ) {
+ $this->settingsRegistry = $settingsRegistry;
+ $this->channelRepository = $channelRepository;
+ $this->settingsFormatter = $settingsFormatter;
+ $this->settingProvider = $settingProvider;
+ $this->optionsResolver = new OptionsResolver();
+
+ $this->configureOptions($this->optionsResolver);
+ }
+
+ public function create(array $options = []): SettingInterface
+ {
+ $options = $this->optionsResolver->resolve($options);
+
+ /** @var SettingsInterface $settings */
+ $settings = $this->settingsRegistry->getByAlias($options['alias']);
+ ['vendor' => $vendor, 'plugin' => $plugin] = $settings->getAliasAsArray();
+
+ $channel = null;
+ if (null !== $options['channel']) {
+ /** @var ?ChannelInterface $channel */
+ $channel = $this->channelRepository->findOneBy(['code' => $options['channel']]);
+ }
+ $setting = $this->settingProvider->getSettingOrCreateNew($vendor, $plugin, $options['path'], $options['locale'], $channel);
+
+ // If it has a type the value was already existing
+ if ($options['ignore_if_exists'] && null !== $setting->getStorageType()) {
+ return $setting;
+ }
+
+ $this->settingProvider->resetExistingValue($setting);
+ $setting->setStorageType($options['type']); // If the type has changed, we change it!
+ $setting->setValue($this->settingsFormatter->formatValue($options['type'], $options['value']));
+
+ return $setting;
+ }
+
+ protected function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver
+ ->setDefault('alias', '')
+ ->setAllowedTypes('alias', 'string')
+ ->setDefault('path', '')
+ ->setAllowedTypes('path', 'string')
+ ->setDefault('channel', null)
+ ->setAllowedTypes('channel', ['null', 'string'])
+ ->setDefault('locale', null)
+ ->setAllowedTypes('locale', ['null', 'string'])
+ ->setDefault('type', SettingInterface::STORAGE_TYPE_TEXT)
+ ->setAllowedTypes('type', 'string')
+ ->setAllowedValues('type', Setting::getAllStorageTypes())
+ ->setDefault('value', null)
+ ->setAllowedTypes('value', ['null', 'string', 'integer', 'bool', 'float', 'Datetime', 'array'])
+ ->setDefault('ignore_if_exists', false)
+ ->setAllowedTypes('ignore_if_exists', 'bool')
+ ;
+ }
+}
diff --git a/src/Fixture/SettingsFixture.php b/src/Fixture/SettingsFixture.php
new file mode 100644
index 00000000..7e769072
--- /dev/null
+++ b/src/Fixture/SettingsFixture.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Fixture;
+
+use Doctrine\ORM\EntityManagerInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Fixture\Factory\SettingsFixtureFactory;
+use Sylius\Bundle\CoreBundle\Fixture\AbstractResourceFixture;
+use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+
+final class SettingsFixture extends AbstractResourceFixture
+{
+ public function __construct(
+ EntityManagerInterface $settingsManager,
+ SettingsFixtureFactory $exampleFactory
+ ) {
+ parent::__construct($settingsManager, $exampleFactory);
+ }
+
+ public function getName(): string
+ {
+ return 'monsieurbiz_settings';
+ }
+
+ protected function configureResourceNode(ArrayNodeDefinition $resourceNode): void
+ {
+ /** @phpstan-ignore-next-line */
+ $resourceNode
+ ->children()
+ ->scalarNode('alias')->cannotBeEmpty()->end()
+ ->scalarNode('path')->cannotBeEmpty()->end()
+ ->scalarNode('channel')->end()
+ ->scalarNode('locale')->end()
+ ->scalarNode('type')->cannotBeEmpty()->end()
+ ->variableNode('value')->end()
+ ->variableNode('ignore_if_exists')->end()
+ ->end()
+ ->end()
+ ;
+ }
+}
diff --git a/src/Formatter/SettingsFormatter.php b/src/Formatter/SettingsFormatter.php
new file mode 100644
index 00000000..07115e45
--- /dev/null
+++ b/src/Formatter/SettingsFormatter.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Formatter;
+
+use DateTime;
+use MonsieurBiz\SyliusSettingsPlugin\Entity\Setting\SettingInterface;
+
+class SettingsFormatter implements SettingsFormatterInterface
+{
+ /**
+ * @param int|float|string|array $value
+ * @param mixed $type
+ *
+ * @return mixed
+ *
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ */
+ public function formatValue($type, $value)
+ {
+ switch ($type) {
+ case SettingInterface::STORAGE_TYPE_BOOLEAN:
+ $value = (bool) $value;
+
+ break;
+ case SettingInterface::STORAGE_TYPE_INTEGER:
+ $value = (int) $value;
+
+ break;
+ case SettingInterface::STORAGE_TYPE_FLOAT:
+ $value = (float) $value;
+
+ break;
+ case SettingInterface::STORAGE_TYPE_JSON:
+ if (!\is_array($value)) {
+ $value = json_decode((string) $value, true);
+ }
+
+ break;
+ case SettingInterface::STORAGE_TYPE_DATE:
+ case SettingInterface::STORAGE_TYPE_DATETIME:
+ if (\is_int($value)) {
+ $value = (new DateTime())->setTimestamp($value);
+
+ break;
+ }
+
+ /** @phpstan-ignore-next-line */
+ $value = new DateTime((string) $value);
+
+ break;
+ }
+
+ return $value;
+ }
+}
diff --git a/src/Formatter/SettingsFormatterInterface.php b/src/Formatter/SettingsFormatterInterface.php
new file mode 100644
index 00000000..321885c1
--- /dev/null
+++ b/src/Formatter/SettingsFormatterInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Formatter;
+
+interface SettingsFormatterInterface
+{
+ /**
+ * @param int|float|string|array $value
+ * @param mixed $type
+ *
+ * @return mixed
+ */
+ public function formatValue($type, $value);
+}
diff --git a/src/Provider/SettingProvider.php b/src/Provider/SettingProvider.php
new file mode 100644
index 00000000..bdb6f47a
--- /dev/null
+++ b/src/Provider/SettingProvider.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Provider;
+
+use Exception;
+use MonsieurBiz\SyliusSettingsPlugin\Entity\Setting\Setting;
+use MonsieurBiz\SyliusSettingsPlugin\Entity\Setting\SettingInterface;
+use MonsieurBiz\SyliusSettingsPlugin\Repository\SettingRepositoryInterface;
+use Sylius\Component\Core\Model\ChannelInterface;
+use Sylius\Component\Resource\Factory\FactoryInterface;
+
+class SettingProvider implements SettingProviderInterface
+{
+ private SettingRepositoryInterface $settingRepository;
+
+ private FactoryInterface $settingFactory;
+
+ public function __construct(
+ SettingRepositoryInterface $settingRepository,
+ FactoryInterface $settingFactory
+ ) {
+ $this->settingRepository = $settingRepository;
+ $this->settingFactory = $settingFactory;
+ }
+
+ public function getSettingOrCreateNew(string $vendor, string $plugin, ?string $path, ?string $locale, ?ChannelInterface $channel): SettingInterface
+ {
+ /** @var SettingInterface|null $setting */
+ $setting = $this->settingRepository->findOneBy([
+ 'vendor' => $vendor,
+ 'plugin' => $plugin,
+ 'path' => $path,
+ 'localeCode' => $locale,
+ 'channel' => $channel,
+ ]);
+
+ if (null === $setting) {
+ /** @var SettingInterface $setting */
+ $setting = $this->settingFactory->createNew();
+ $setting->setVendor($vendor);
+ $setting->setPlugin($plugin);
+ $setting->setPath($path);
+ $setting->setLocaleCode($locale);
+ $setting->setChannel($channel);
+ }
+
+ return $setting;
+ }
+
+ public function validateType(?string $type): void
+ {
+ $types = Setting::getAllStorageTypes();
+ if (!\in_array($type, $types, true)) {
+ throw new Exception(sprintf('The type "%s" is not valid. Valid types are: %s', $type, implode(', ', $types)));
+ }
+ }
+
+ public function resetExistingValue(SettingInterface $setting): SettingInterface
+ {
+ if (null !== $setting->getStorageType()) {
+ // Reset existing value
+ $setting->setValue(null);
+ }
+
+ return $setting;
+ }
+}
diff --git a/src/Provider/SettingProviderInterface.php b/src/Provider/SettingProviderInterface.php
new file mode 100644
index 00000000..7a192283
--- /dev/null
+++ b/src/Provider/SettingProviderInterface.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace MonsieurBiz\SyliusSettingsPlugin\Provider;
+
+use MonsieurBiz\SyliusSettingsPlugin\Entity\Setting\SettingInterface;
+use Sylius\Component\Core\Model\ChannelInterface;
+
+interface SettingProviderInterface
+{
+ public function getSettingOrCreateNew(string $vendor, string $plugin, ?string $path, ?string $locale, ?ChannelInterface $channel): SettingInterface;
+
+ public function validateType(?string $type): void;
+
+ public function resetExistingValue(SettingInterface $setting): SettingInterface;
+}
diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index 2abb4761..b18a5153 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -52,3 +52,17 @@ services:
class: MonsieurBiz\SyliusSettingsPlugin\Provider\SettingsProvider
MonsieurBiz\SyliusSettingsPlugin\Provider\SettingsProviderInterface: '@monsieurbiz.settings.provider'
+
+ monsieurbiz.setting.provider:
+ class: MonsieurBiz\SyliusSettingsPlugin\Provider\SettingProvider
+
+ MonsieurBiz\SyliusSettingsPlugin\Provider\SettingProviderInterface: '@monsieurbiz.setting.provider'
+
+ MonsieurBiz\SyliusSettingsPlugin\Fixture\:
+ resource: '../../Fixture'
+
+ MonsieurBiz\SyliusSettingsPlugin\Command\:
+ resource: '../../Command'
+
+ MonsieurBiz\SyliusSettingsPlugin\Formatter\:
+ resource: '../../Formatter'