In the last article we discussed the Source Model and today, we’ll delve into the advantages of the Backend Model. When we save values in configuration of e.g. our previously created model, various actions take place. Thanks to the Backend Model we are able to control the actions prior to saving (using the beforeSave() method) and afterwards (with afterSave()). What can we achieve this way? Anything you can imagine!

Before we move on to creating our custom backend models, let’s take a look at several examples which are already in place in Magento vendor. For example, class Magento\Cookie\Model\Config\Backend\Lifetime includes the beforeSave() method which, prior to saving the configuration data for the Cookie lifetime field, checks if the field isn’t empty and if the value is correct (it’s being checked by a separate class). Another example is the class Magento\CatalogInventory\Model\Config\Backend\Backorders. It includes the afterSave() method which, after saving the data, checks if the value has changed and if it did, changes the Indexes to invalid.

All Backend Model classes (indirectly or directly) inherit from the class Magento\Framework\App\Config\Value. This class enables us to use many interesting methods that facilitate writing our own Backend Model including, but not limited to: getValue(), isValueChanged() or getOldValue().

But enough theory – let’s move to practice! Imagine that we want the value of our field to always be a fixed positive number. There are 3 conditions that need to be checked before saving – whether the value has been entered at all, is the input value a number, and is it higher than zero. If they are not met the save will fail, because otherwise it would have crashed our store. We also want to ensure that it’s not possible to enter anything else than the required value.

Let’s add a new field group containing our field to the previously created file app/code/Magently/Tutorial/etc/adminhtml/system.xml:

//file app/code/Magently/Tutorial/etc/adminhtml/system.xml

...
            <group id="test_backend" 
            translate="label" 
            type="text" 
            sortOrder="1" 
            showInDefault="1" 
            showInWebsite="1" 
            showInStore="1">
                <label>Backend model test</label>
                <field id="custom_backend_model" 
                    translate="label" 
                    type="text" 
                    sortOrder="1" 
                    showInDefault="1" 
                    showInWebsite="1" 
                    showInStore="1">
                    <label>Custom backend model</label>
                    <backend_model>Magently\Tutorial\Model\Config\Backend\Custom</backend_model>
                </field>
            </group>
..

Next, let’s create our custom Backend Model using the beforeSave() method:

//file app/code/Magently/Tutorial/Model/Config/Backend/Custom.php

<?php

namespace Magently\Tutorial\Model\Config\Backend;

class Custom extends \Magento\Framework\App\Config\Value
{
    public function beforeSave()
    {
        $label = $this->getData('field_config/label');

        if ($this->getValue() == '') {
            throw new \Magento\Framework\Exception\ValidatorException(__($label . ' is required.'));
        } else if (!is_numeric($this->getValue())) {
            throw new \Magento\Framework\Exception\ValidatorException(__($label . ' is not a number.'));
        } else if ($this->getValue() < 0) {
            throw new \Magento\Framework\Exception\ValidatorException(__($label . ' is less than 0.'));
        }

        $this->setValue(intval($this->getValue()));

        parent::beforeSave();
    }
}

Now, whenever someone tries to enter a value different than required, they will receive information similar to the one below:

screenshot1

Moving on, let’s use the afterSave() method in this model in order to save a completely new entry in the database. This entry will use the value entered in our field. We can also overwrite a value of another field if we give the correct path. In order to save the new value, we just have to add a constructor to the previously created file in which we put the class \Magento\Framework\App\Config\ValueFactory as a Dependency Injection. We also create a constant with the path where our new value should be saved. As a result, the file looks as follows:

//file app/code/Magently/Tutorial/Model/Config/Backend/Custom.php

<?php

namespace Magently\Tutorial\Model\Config\Backend;

class Custom extends \Magento\Framework\App\Config\Value
{

    const CUSTOM_OPTION_STRING_PATH = 'magently_tutorial_test/test_backend/another_option';
    protected $_configValueFactory;
    /**
     * @param \Magento\Framework\Model\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
     * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList
     * @param \Magento\Framework\App\Config\ValueFactory $configValueFactory
     * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
     * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
     * @param string $runModelPath
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\App\Config\ScopeConfigInterface $config,
        \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList,
        \Magento\Framework\App\Config\ValueFactory $configValueFactory,
        \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        $this->_configValueFactory = $configValueFactory;

        parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
    }

    public function beforeSave()
    {
        $label = $this->getData('field_config/label');

        if ($this->getValue() == '') {
            throw new \Magento\Framework\Exception\ValidatorException(__($label . ' is required.'));
        } else if (!is_numeric($this->getValue())) {
            throw new \Magento\Framework\Exception\ValidatorException(__($label . ' is not a number.'));
        } else if ($this->getValue() < 0) {
            throw new \Magento\Framework\Exception\ValidatorException(__($label . ' is less than 0.'));
        }

        $this->setValue(intval($this->getValue()));

        parent::beforeSave();
    }

    public function afterSave()
    {
        $value = $this->getValue() . '_SOMETHING_NEW';

        try {
            $this->_configValueFactory->create()->load(
                self::CUSTOM_OPTION_STRING_PATH,
                'path'
            )->setValue(
                $value
            )->setPath(
                self::CUSTOM_OPTION_STRING_PATH
            )->save();
        } catch (\Exception $e) {
            throw new \Exception(__('We can\'t save new option.'));
        }

        return parent::afterSave();
    }
}

As you can see, the Backend Model gives us control over all elements that are being updated in our module’s configuration and ensures that we can avoid future complications.

That’s it for now, but watch out for more to come! In the last part of this short series we’ll focus on the Frontend Model, which will allow us, among others, to add and remove custom fields.

 

Magento 2 Backend Configuration: Source Model part 1/3

Magento 2 Backend Configuration: Source Model part 3/3