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'