Magento 2 Developement

Magento 2: Working with ArraySerialized backend model

Complete module code can be found on github.
Ready-to-paste package – magento2_configuration_arrayserialized.zip.
Installation instructions

Magento 2 ArraySerialized is backend model used to store configuration data ( dynamic settings array ) in serialized state in one field. It extends \Magento\Config\Model\Config\Backend\Serialized model and has two basic methods:

beforeSave serializes data before saving, as a result options converted to a string and can be stored in a single field in the database

    /**
     * @return $this
     */
    public function beforeSave()
    {
        if (is_array($this->getValue())) {
            $this->setValue($this->serializer->serialize($this->getValue()));
        }
        parent::beforeSave();
        return $this;
    }

_afterLoad unserialize value before return it to caller.

/**
     * @return void
     */
    protected function _afterLoad()
    {
        $value = $this->getValue();
        if (!is_array($value)) {
            $this->setValue(empty($value) ? false : $this->serializer->unserialize($value));
        }
    }

Magento 2.0.x and Magento 2.1.x use default PHP serialize functionality. Starting from Magento 2.2.x it uses the JSON format by default.

Implementation

Dynamic settings in system configuration is really cozy when you don’t know exact number of parameters. Here is sample screenshot from Magento 2 Product Slider extension by NWDthemes. It allows you to control number of items in slider for certain screen width. You can add and delete options according to your needs.

1. Add new field in /etc/adminhtml/system.xml.

<field id="responsiveItems" translate="label" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
    <label>Responsive Items</label>
    <frontend_model>Nwdthemes\ArraySerialized\Block\Adminhtml\Form\Field\ResponsiveItems</frontend_model>
    <backend_model>Magento\Config\Model\Config\Backend\Serialized\ArraySerialized</backend_model>
    <comment>Object containing responsive options. Breakpoint set screen width and item set number of items visible starting from breakpoint width</comment>
</field>

It use ArraySerialized as backend model.

2. Create frontend model Block/Adminhtml/Form/Field/ResponsiveItems.php. We use text field for sample, but it can be any html element like checkbox, dropdown, radio button etc.

<?php

namespace Nwdthemes\ArraySerialized\Block\Adminhtml\Form\Field;

use Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray;

/**
 * Class ResponsiveItems
 */
class ResponsiveItems extends AbstractFieldArray
{
    /**
     * {@inheritdoc}
     */
    protected function _prepareToRender()
    {
        $this->addColumn('breakpoint', ['label' => __('Breakpoint ( in px )'), 'class' => 'required-entry']);
        $this->addColumn('items', ['label' => __('Items'), 'class' => 'required-entry']);
        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add Breakpoint');
    }
}

$this->addColumn() add columns to settings array. Function take two params:

  1. $name – define column name
  2. $params – column parameters array. Possible parameters are:
    • ‘label’
    • ‘size’
    • ‘style’
    • ‘class’
    • ‘renderer’

Thats it. Clear cache if enabled and you can see new field in magento admin.

Default values for system configuration

Usually default values for configuration set in etc/config.xml. But it does not work with ArraySerialized in Magento 2.0.x – 2.1.x. Check this issue on github for details. To resolve it we will use Setup/InstallData.php. Data setup used to insert default module data into database. Attributes that come with Magento by default, 404 and other Cms pages are all examples of data setup.

How data stored in database

Magento 2.0.x – 2.1.x use default PHP serialize functionality.

a:4:{s:17:"_1528879546018_18";a:2:{s:10:"breakpoint";s:1:"0";s:5:"items";s:1:"1";}s:18:"_1528879546554_554";a:2:{s:10:"breakpoint";s:3:"480";s:5:"items";s:1:"2";}s:17:"_1528879547050_50";a:2:{s:10:"breakpoint";s:3:"768";s:5:"items";s:1:"3";}s:18:"_1528879555818_818";a:2:{s:10:"breakpoint";s:4:"1024";s:5:"items";s:1:"5";}}

Magento 2.2.x use the JSON format by default.

{"_1528878332307_307":{"breakpoint":"0","items":"1"},"_1528878332987_987":{"breakpoint":"480","items":"2"},"_1528878338219_219":{"breakpoint":"768","items":"3"},"_1528878340675_675":{"breakpoint":"1024","items":"5"}}

Array keys like _1528879546018_18 generated by magento when you add new option to your dynamic settings. Its purpose is just to generate unique element ID. Javascript code that add new row use time and milliseconds to generate it. We will use this info to generate default value.

add: function(rowData, insertAfterId) {
	// generate default template data
	var templateValues;

	// Prepare template values
	if (rowData) {
		templateValues = rowData;
	} else {
		var d = new Date();
		templateValues = {
			breakpoint: '',
			'option_extra_attrs': {},
			items: '',
			'option_extra_attrs': {},
			_id: '_' + d.getTime() + '_' + d.getMilliseconds()
	};
}

Add default value

<?php

namespace Nwdthemes\ArraySerialized\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * Config factory
     * @var \Magento\Config\Model\Config\Factory
     */
    private $configFactory;

    /**
     * Init
     *
     * @param \Magento\Config\Model\Config\Factory $configFactory
     * @param \Magento\Framework\App\State $state
     */
    public function __construct(
        \Magento\Config\Model\Config\Factory $configFactory,
        \Magento\Framework\App\State $state
    )
    {
        $this->configFactory = $configFactory;
        $state->setAreaCode('adminhtml');
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $index = time();
        $configData = [
            'section' => 'nwdarrayserialized',
            'website' => null,
            'store'   => null,
            'groups'  => [
                'general' => [
                    'fields' => [
                        'responsiveItems' => [
                            'value' => array(
                                $index.'_0' => array("breakpoint" => "0", "items" => "1"),
                                $index.'_1' => array("breakpoint" => "480", "items" => "2"),
                                $index.'_2' => array("breakpoint" => "768", "items" => "3"),
                                $index.'_3' => array("breakpoint" => "1024", "items" => "5"),
                            ),
                        ],
                    ],
                ],
            ],
        ];

        /** @var \Magento\Config\Model\Config $configModel */
        $configModel = $this->configFactory->create(['data' => $configData]);
        $configModel->save();
    }

}

$state->setAreaCode('adminhtml'); line in constructor set store area. Area is not set in Magento CLI (it is not required for any core commands). When you try to run setup:upgrade without the area you will get the error:

[Magento\Framework\Exception\SessionException] Area code not set: Area code must be set before starting a session.

[Magento\Framework\Exception\LocalizedException] Area code is not set

Data script use \Magento\Config\Model\Config model to save default value. We pass array to the model, but it needs to be serialized before save. Config model check if field has backend model and use it for data processing. Check Mage2.PRO for complete code flow of how magento 2 save a backend config form vield value.

Starting from magento 2.2.4 you should be able to set default value directly in etc/config.xml. Check this commit for a fix and usage sample.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <test_section>
            <test_group>
                 <test_field_serialized backend_model="Magento\Config\Model\Config\Backend\Serialized">["value1","value2"]</test_field_serialized>
            </test_group>
        </test_section>
    </default>
</config>

Using dynamic settings in your models / blocks / templates

To use dynamic settings in module we will create a helper.

<?php

namespace Nwdthemes\ArraySerialized\Helper;

use Magento\Framework\App\ObjectManager;

/**
 * ArraySerialized helper
 */
class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * Get config value
     * @param $configPath
     * @param null $store
     * @return mixed
     */
    public function getConfigValue($configPath, $store = null)
    {
        return $this->scopeConfig->getValue(
            $configPath,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
            $store
        );
    }

    /**
     * Get serialized config value
     * temporarily solution to get unserialized config value
     * should be deprecated in 2.3.x
     *
     * @param $configPath
     * @param null $store
     * @return mixed
     */
    public function getSerializedConfigValue($configPath, $store = null)
    {
        $value = $this->getConfigValue($configPath, $store);

        if (empty($value)) return false;

        if ($this->isSerialized($value)) {
            $unserializer = ObjectManager::getInstance()->get(\Magento\Framework\Unserialize\Unserialize::class);
        } else {
            $unserializer = ObjectManager::getInstance()->get(\Magento\Framework\Serialize\Serializer\Json::class);
        }

        return $unserializer->unserialize($value);
    }

    /**
     * Check if value is a serialized string
     *
     * @param string $value
     * @return boolean
     */
    private function isSerialized($value)
    {
        return (boolean) preg_match('/^((s|i|d|b|a|O|C):|N;)/', $value);
    }

}

public function getConfigValue() used to load any value from config table.
public function getSerializedConfigValue() is modified to automatically unserialize config data. It detect if we have serialized string or json string and use proper method to unserialize data. This code works in both 2.0.x – 2.1.x and 2.2.x versions. Starting from 2.2.4 it should be enough to use just public function getConfigValue().

Sample block to illustrate helper usage.

<?php

namespace Nwdthemes\ArraySerialized\Block;

use Magento\Framework\View\Element\Template;

class Sample extends Template
{
    /**
     * @var \Nwdthemes\ArraySerialized\Helper\Data
     */
    protected $helper;

    /**
     * @param Template\Context $context
     * @param \Nwdthemes\ArraySerialized\Helper\Data $helper
     * @param array $data
     */
    public function __construct(Template\Context $context, \Nwdthemes\ArraySerialized\Helper\Data $helper, array $data = [])
    {
        $this->helper = $helper;
        parent::__construct($context, $data);
    }

    /**
     * Return string with json configuration
     *
     * @return string
     */
    public function getArraySerialized()
    {
        $responsiveItems = $this->helper->getSerializedConfigValue('nwdarrayserialized/general/responsiveItems');
        //process value according to your needs...
        return json_encode($responsiveItems);
    }
}

We inject helper in block constructor and use it to grab the data from configuration. Complete module code can be found on github.

Installation

Install via composer (recommend)

Run the following command in Magento 2 root folder:

composer require elpas0/magento2-configuration-arrayserialized
php bin/magento setup:upgrade
php bin/magento setup:static-content:deploy
php bin/magento cache:clean

Check this article if you have for details – install magento 2 module using github

Install ready-to-paste package

Uninstallation

Magento 2: How to disable module
Magento 2: How to uninstall module

4 thoughts on “Magento 2: Working with ArraySerialized backend model

  1. Hi, i got “my_variable is not defined” when trying to use this code in Block/Adminhtml/Form/Field/ResponsiveItems.php:
    $this->addColumn(‘subject_email’, [‘label’ => __(‘Subject Email’)]);

    This only happen when i do this the second time. On first time i put 2 column and it worked perfectly:
    $this->addColumn(‘subject_code’, [‘label’ => __(‘Subject Code’)]);
    $this->addColumn(‘subject_label’, [‘label’ => __(‘Subject Label’)]);

    Please help, thanks

  2. Hi
    Can you add more details?
    1. What magento version you have?
    2. Could you add full code of ResponsiveItems.php file to check the error?

  3. HI,
    I am using 2.2.6 with third party extension for creating customer address attribute.It seem like the extension is build with copying files from Magento2 commerce edition.
    we are facing issue where dropdown type of attribute is not able to save the attribute values from admin.
    I debug and found the code is using Serialize form data.
    Any thoughts what can be wrong here ?

    Thank you

Leave a Reply

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