Create module configuration using Symfony services and components in PrestaShop 8

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.

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 PrestaShopPrestaShopAdapterSymfonyContainer;

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: 'PrestaShopModuleWkCreateDemoProductControllerDemoProductConfigurationController::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 wkcreatedemoproductForm folder

services:
  _defaults:
    public: true
  prestashop.module.wkcreatedemoproduct.form.type.demo_configuration_text:
    class: 'PrestaShopModuleWkCreateDemoProductFormDemoConfigurationTextType'
    parent: 'form.type.translatable.aware'
    public: true
    tags:
      - { name: form.type }

  prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_provider:
    class: 'PrestaShopModuleWkCreateDemoProductFormDemoConfigurationTextFormDataProvider'
    arguments:
      - '@prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_data_configuration'

  prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_handler:
    class: 'PrestaShopPrestaShopCoreFormHandler'
    arguments:
      - '@form.factory'
      - '@prestashop.core.hook.dispatcher'
      - '@prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_form_data_provider'
      - 'PrestaShopModuleWkCreateDemoProductFormDemoConfigurationTextType'
      - 'DemoConfiguration'

  prestashop.module.wkcreatedemoproduct.form.demo_configuration_text_data_configuration:
    class: PrestaShopModuleWkCreateDemoProductFormDemoConfigurationTextDataConfiguration
    arguments: ['@prestashop.adapter.legacy.configuration']

Step: 4 Create services class

Now create a service class in the wkcreatedemoproductForm folder

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

declare(strict_types=1);

namespace PrestaShopModuleWkCreateDemoProductForm;

use PrestaShopBundleFormAdminTypeFormattedTextareaType;
use PrestaShopBundleFormAdminTypeGeneratableTextType;
use PrestaShopBundleFormAdminTypeMoneyWithSuffixType;
use SymfonyComponentFormExtensionCoreTypeNumberType;
use PrestaShopBundleFormAdminTypeTranslatableType;
use PrestaShopBundleFormAdminTypeTranslatorAwareType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentValidatorConstraintsLength;

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 PrestaShopModuleWkCreateDemoProductForm;

use PrestaShopPrestaShopCoreConfigurationDataConfigurationInterface;
use PrestaShopPrestaShopCoreConfigurationInterface;
use PrestaShopPrestaShopAdapterLanguageLanguageDataProvider;
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;
        }
    }
}

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

declare(strict_types=1);

namespace PrestaShopModuleWkCreateDemoProductForm;

use PrestaShopPrestaShopCoreConfigurationDataConfigurationInterface;
use PrestaShopPrestaShopCoreFormFormDataProviderInterface;

/**
 * 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 PrestaShopModuleWkCreateDemoProductController;

use PrestaShopBundleControllerAdminFrameworkBundleAdminController;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;

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.

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]


Source link