Reading list Switch to dark mode

    Create module configuration using Symfony services and components in PrestaShop 8

    Updated 31 March 2023

    In this blog, we will learn how to create configurations using Symfony services and components and will define our own Symfony services in the module, and will use existing components.

    Here, we are showing the process step by step with an example module ‘wkcreatedemoproduct

    Step 1: Create the module class

    here, we have created the module class file “wkcreatedemoproduct/wkcreatedemoproduct.php

    In this class, we are initializing the module, controllers tab, and redirecting the configuration page to modern page routes.

    Start your headless eCommerce
    now.
    Find out More

    Also, note that we are using a new translation system in this example module. Please refer to the documentation for more information.

    declare(strict_types=1);
    
    use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
    
    class WkCreateDemoProduct extends Module
    {
        public function __construct()
        {
            $this->name = 'wkcreatedemoproduct';
            $this->author = 'Webkul';
            $this->version = '1.0.0';
            $this->need_instance = 0;
    
            $this->bootstrap = true;
            parent::__construct();
    
            $this->displayName = $this->trans('Create demo products', [], 'Modules.Wkcreatedemoproduct.Admin');
            $this->description = $this->trans(
                'Module created for the Demo product creation purpose on PrestaShop store',
                [],
                'Modules.Wkcreatedemoproduct.Admin'
            );
    
            $this->ps_versions_compliancy = ['min' => '8.0.0', 'max' => '8.99.99'];
        }
    
        public function getTabs()
        {
            return [
                [
                    'class_name' => 'AdminWkCreateDemoProduct', // Creating tab in admin menu
                    'visible' => true,
                    'name' => 'Demo Product Data',
                    'parent_class_name' => 'CONFIGURE',
                ]
            ];
        }
    
        public function getContent()
        {
            $route = SymfonyContainer::getInstance()->get('router')->generate('demo_product_configuration_form');
            // redirecting to controller, symfony route
            Tools::redirectAdmin($route);
        }
    
        public function isUsingNewTranslationSystem()
        {
            // Used this for new translations system, It will avoid redirection of translation page on legacy controller
            return true;
        }
    }

    Step 2: Define routes

    Create routes.yml file inside the wkcreatedemoproduct/config folder

    In this file, we have provided the path from the legacy controller to the modern page controller

    demo_product_configuration_form:
      path: /wkcreatedemoproduct/configuration
      methods: [GET, POST]
      defaults:
        _controller: 'PrestaShop\Module\WkCreateDemoProduct\Controller\DemoProductConfigurationController::index'
        # Needed to work with tab system
        _legacy_controller: AdminWkCreateDemoProduct
        _legacy_link: AdminWkCreateDemoProduct

    Step 3: Define services

    Create a services.yml file inside the wkcreatedemoproduct/config folder

    In the given code, we have defined our own services. These will be used for form creation and form handling. Each service is created inside the wkcreatedemoproduct\Form folder

    services:
      _defaults:
        public: true
      prestashop.module.wkcreatedemoproduct.form.type.demo_configuration_text:
        class: 'PrestaShop\Module\WkCreateDemoProduct\Form\DemoConfigurationTextType'
        parent: 'form.type.translatable.aware'
        public: true
        tags:
          - { name: form.type }
    
      prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_provider:
        class: 'PrestaShop\Module\WkCreateDemoProduct\Form\DemoConfigurationTextFormDataProvider'
        arguments:
          - '@prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_data_configuration'
    
      prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_handler:
        class: 'PrestaShop\PrestaShop\Core\Form\Handler'
        arguments:
          - '@form.factory'
          - '@prestashop.core.hook.dispatcher'
          - '@prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_provider'
          - 'PrestaShop\Module\WkCreateDemoProduct\Form\DemoConfigurationTextType'
          - 'DemoConfiguration'
    
      prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_data_configuration:
        class: PrestaShop\Module\WkCreateDemoProduct\Form\DemoConfigurationTextDataConfiguration
        arguments: ['@prestashop.adapter.legacy.configuration']

    Step: 4 Create services class

    Now create a service class in the wkcreatedemoproduct\Form folder

    i) First service (demo_configuration_text) class DemoConfigurationTextType is created for form view

    declare(strict_types=1);
    
    namespace PrestaShop\Module\WkCreateDemoProduct\Form;
    
    use PrestaShopBundle\Form\Admin\Type\FormattedTextareaType;
    use PrestaShopBundle\Form\Admin\Type\GeneratableTextType;
    use PrestaShopBundle\Form\Admin\Type\MoneyWithSuffixType;
    use Symfony\Component\Form\Extension\Core\Type\NumberType;
    use PrestaShopBundle\Form\Admin\Type\TranslatableType;
    use PrestaShopBundle\Form\Admin\Type\TranslatorAwareType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Validator\Constraints\Length;
    
    class DemoConfigurationTextType extends TranslatorAwareType
    {
        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options): void
        {
            $builder
                ->add('product_name', TranslatableType::class, [
                    'label' => $this->trans('First product name', 'Modules.Wkcreatedemoproduct.Admin'),
                    'required' => true,
                    'options' => [
                        'constraints' => [
                            new Length([
                                'max' => 128,
                            ]),
                        ],
                    ],
                ])
                ->add('product_prefix', GeneratableTextType::class, [
                    'label' => $this->trans('Name prefix', 'Modules.Wkcreatedemoproduct.Admin'),
                    'generated_value_length' => 5,
                ])
                ->add('product_short_desc', TranslatableType::class, [
                    'label' => $this->trans('Product short description', 'Modules.Wkcreatedemoproduct.Admin'),
                    'type' => FormattedTextareaType::class,
                    'help' => $this->trans('Character limit < 21844 and text does not contains <>={}', 'Modules.Wkcreatedemoproduct.Admin'),
                    'required' => true,
                    'options' => [
                        'constraints' => [
                            new Length([
                                'max' => 21844,
                            ]),
                        ],
                    ],
                ])
                ->add('product_qty', NumberType::class, [
                    'label' => $this->trans('Product quantity', 'Modules.Wkcreatedemoproduct.Admin'),
                    'unit' => 'unit',
                ]);
        }
    }

    ii) Second service (demo_configuration_text_data_configuration) class DemoConfigurationTextDataConfiguration is created for the data process

    declare(strict_types=1);
    
    namespace PrestaShop\Module\WkCreateDemoProduct\Form;
    
    use PrestaShop\PrestaShop\Core\Configuration\DataConfigurationInterface;
    use PrestaShop\PrestaShop\Core\ConfigurationInterface;
    use PrestaShop\PrestaShop\Adapter\Language\LanguageDataProvider;
    use Validate;
    use Tools;
    
    /**
     * Configuration is used to save data to configuration table and retrieve from it
     */
    final class DemoConfigurationTextDataConfiguration implements DataConfigurationInterface
    {
        public const WK_PRODUCT_NAME = 'WK_DEMO_PRODUCT_WK_PRODUCT_NAME';
        public const WK_PRODUCT_SHORT_DESC = 'WK_DEMO_PRODUCT_WK_PRODUCT_SHORT_DESC';
        public const WK_PRODUCT_PREFIX = 'WK_DEMO_PRODUCT_WK_PRODUCT_PREFIX';
        public const WK_PRODUCT_QTY = 'WK_DEMO_PRODUCT_WK_PRODUCT_QTY';
        /**
         * @var ConfigurationInterface
         */
        private $configuration;
    
        /**
         * @param ConfigurationInterface $configuration
         */
        public function __construct(ConfigurationInterface $configuration)
        {
            $this->configuration = $configuration;
        }
    
        /**
         * {@inheritdoc}
         */
        public function getConfiguration(): array
        {
            $return = [];
    
            if ($productName = $this->configuration->get(static::WK_PRODUCT_NAME)) {
                $return['product_name'] = $productName;
            }
            if ($productShortDesc = $this->configuration->get(static::WK_PRODUCT_SHORT_DESC)) {
                $return['product_short_desc'] = $productShortDesc;
            }
            if ($productPrefix = $this->configuration->get(static::WK_PRODUCT_PREFIX)) {
                $return['product_prefix'] = $productPrefix;
            }
            if ($productQty = $this->configuration->get(static::WK_PRODUCT_QTY)) {
                $return['product_qty'] = $productQty;
            }
    
            return $return;
        }
    
        /**
         * {@inheritdoc}
         */
        public function updateConfiguration(array $configuration): array
        {
            $errors = $this->validateConfiguration($configuration);
            if (!empty($errors)) {
                return $errors;
            }
            $this->configuration->set(static::WK_PRODUCT_NAME, $configuration['product_name']);
            $this->configuration->set(static::WK_PRODUCT_SHORT_DESC, $configuration['product_short_desc'], null, ['html' => true]);
            /* Adding html => true allows for configuration->set to save HTML values */
            $this->configuration->set(static::WK_PRODUCT_PREFIX, $configuration['product_prefix']);
            $this->configuration->set(static::WK_PRODUCT_QTY, $configuration['product_qty']);
    
            /* Errors are returned here. */
            return [];
        }
    
        /**
         * Ensure the parameters passed are valid.
         *
         * @param array $configuration
         *
         * @return bool Returns array
         */
        public function validateConfiguration(array $configuration): array
        {
            $errors = [];
            $objLangDataProvider = new LanguageDataProvider();
            $languages = $objLangDataProvider->getLanguages(false);
            foreach ($languages as $lang) {
                if (!trim($configuration['product_name'][$lang['id_lang']])) {
                    $errors[] = sprintf(
                        $this->l('Product name is required in %s.'),
                        $lang['name']
                    );
                } elseif (!Validate::isCatalogName($configuration['product_name'][$lang['id_lang']])) {
                    $errors[] = sprintf(
                        $this->l('Product name is not valid in %s.'),
                        $lang['name']
                    );
                } elseif (Tools::strlen($configuration['product_name'][$lang['id_lang']]) > 128) {
                    $this->errors[] = sprintf(
                        $this->trans('Product name is too long. It must have 128 character or less in %s.', 'Modules.Wkcreatedemoproduct.Admin'),
                        $lang['name']
                    );
                }
                if (!trim($configuration['product_short_desc'][$lang['id_lang']])) {
                    $errors[] = sprintf(
                        $this->trans('Product short description is required in %s.', 'Modules.Wkcreatedemoproduct.Admin'),
                        $lang['name']
                    );
                } elseif (!Validate::isCleanHtml($configuration['product_short_desc'][$lang['id_lang']])) {
                    $errors[] = sprintf(
                        $this->trans('Product short description is not valid in %s.', 'Modules.Wkcreatedemoproduct.Admin'),
                        $lang['name']
                    );
                } elseif (Tools::strlen($configuration['product_short_desc'][$lang['id_lang']]) > 21844) {
                    $this->errors[] = sprintf(
                        $this->trans('Product short description too long. It must have 21844 character or less in %s.', 'Modules.Wkcreatedemoproduct.Admin'),
                        $lang['name']
                    );
                }
                if (!$configuration['product_qty']) {
                    $errors[] = $this->trans('Product quantity is required.', 'Modules.Wkcreatedemoproduct.Admin');
                } elseif (!Validate::isUnsignedInt($configuration['product_qty'])) {
                    $errors[] = $this->trans('Product quantity is invalid.', 'Modules.Wkcreatedemoproduct.Admin');
                }
                return $errors;
            }
        }
    }

    iii) Third service (demo_configuration_text_form_data_provider) class DemoConfigurationTextFormDataProvider is created for getter and setter purpose

    declare(strict_types=1);
    
    namespace PrestaShop\Module\WkCreateDemoProduct\Form;
    
    use PrestaShop\PrestaShop\Core\Configuration\DataConfigurationInterface;
    use PrestaShop\PrestaShop\Core\Form\FormDataProviderInterface;
    
    /**
     * Provider ir responsible for providing form data, in this case it's as simple as using configuration to do that
     *
     * Class DemoConfigurationTextFormDataProvider
     */
    class DemoConfigurationTextFormDataProvider implements FormDataProviderInterface
    {
        /**
         * @var DataConfigurationInterface
         */
        private $demoConfigurationTextDataConfiguration;
    
        /**
         * @param DataConfigurationInterface $demoConfigurationTextDataConfiguration
         */
        public function __construct(DataConfigurationInterface $demoConfigurationTextDataConfiguration)
        {
            $this->demoConfigurationTextDataConfiguration = $demoConfigurationTextDataConfiguration;
        }
    
        /**
         * {@inheritdoc}
         */
        public function getData(): array
        {
            return $this->demoConfigurationTextDataConfiguration->getConfiguration();
        }
    
        /**
         * {@inheritdoc}
         */
        public function setData(array $data): array
        {
            return $this->demoConfigurationTextDataConfiguration->updateConfiguration($data);
        }
    }

    Step 5: Create a controller

    DemoProductConfigurationController controller is created to handle the request, It must be inside the wkcreatedemoproduct/src/Controller folder

    declare(strict_types=1);
    
    namespace PrestaShop\Module\WkCreateDemoProduct\Controller;
    
    use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    class DemoProductConfigurationController extends FrameworkBundleAdminController
    {
        public function index(Request $request): Response
        {
            $textFormDataHandler = $this->get('prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_handler');
    
            $textForm = $textFormDataHandler->getForm();
            $textForm->handleRequest($request);
    
            if ($textForm->isSubmitted() && $textForm->isValid()) {
                /** You can return array of errors in form handler and they can be displayed to user with flashErrors */
                $errors = $textFormDataHandler->save($textForm->getData());
                if (empty($errors)) {
                    $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
    
                    return $this->redirectToRoute('demo_product_configuration_form');
                }
    
                $this->flashErrors($errors);
            }
    
            return $this->render('@Modules/wkcreatedemoproduct/views/templates/admin/form.html.twig', [
                'demoConfigurationTextForm' => $textForm->createView(),
            ]);
        }
    }

    Step 6: Create view

    Twig file form.html.twig path: wkcreatedemoproduct/views/templates/admin/

    {% extends '@PrestaShop/Admin/layout.html.twig' %}
    
    {# PrestaShop made some modifications to the way forms are displayed to work well with PrestaShop in order to benefit from those you need to use ui kit as theme#}
    {% form_theme demoConfigurationTextForm '@PrestaShop/Admin/TwigTemplateForm/prestashop_ui_kit.html.twig' %}
    
    {% block content %}
      {{ form_start(demoConfigurationTextForm) }}
      <div class="card">
        <h3 class="card-header">
          <i class="material-icons">settings</i> {{ 'Demo products settings'|trans({}, 'Modules.Wkcreatedemoproduct.Admin') }}
        </h3>
        <div class="card-body">
          <div class="form-wrapper">
            {{ form_widget(demoConfigurationTextForm) }}
          </div>
        </div>
        <div class="card-footer">
          <div class="d-flex justify-content-end">
            <button class="btn btn-primary float-right" id="save-button">
              {{ 'Create sample products'|trans({}, 'Modules.Wkcreatedemoproduct.Admin') }}
            </button>
          </div>
        </div>
      </div>
      {{ form_end(demoConfigurationTextForm) }}
    {% endblock %}
    
    {% block javascripts %}
      {{ parent() }}
      <script src="{{ asset('../modules/wkcreatedemoproduct/views/js/admin.js') }}"></script>
    {% endblock %}

    Step 7: Define the composer file

    Create composer.json file for autoloading

    {
        "name": "prestashop/wkcreatedemoproduct",
        "description": "PrestaShop - DEMO Products",
        "license": "AFL-3.0",
        "authors": [{
            "name": "Webkul"
        }],
        "autoload": {
            "psr-4": {
                "PrestaShop\\Module\\WkCreateDemoProduct\\": "src/"
            }
        },
        "require": {
            "php": ">=7.1.0"
        },
        "config": {
            "preferred-install": "dist",
            "prepend-autoloader": false
        },
        "type": "prestashop-module"
    }

    Step 8: Installation

    Copy created a module on the modules directory of PrestaShop and run the composer command composer install to install all dependencies. The complete module folder structure is attached in the screenshot.

    screenshot-2023.03.30-17_37_48

    After installing the module, you will controller tab in the left menu sidebar and the configuration form

    screenshot-2023.03.30-17_42_07
    screenshot-2023.03.30-17_46_01

    In the above example, we have created some fields using components, check the documentation for more information.

    That’s all about this blog.

    If any issues or doubts please feel free to mention them in the comment section.

    I would be happy to help.

    Also, you can explore our PrestaShop Development Services & a large range of quality PrestaShop Modules.

    For any doubt contact us at [email protected]

    . . .

    Leave a Comment

    Your email address will not be published. Required fields are marked*


    Be the first to comment.

    Back to Top

    Message Sent!

    If you have more details or questions, you can reply to the received confirmation email.

    Back to Home