System Configuration 

With the help of OroConfigBundle, you can define configuration settings in different scopes. These settings can be organized and visualized in different configuration trees.

$config = $this->get('oro_config.user');
$value  = $config->get('oro_anybundle.anysetting');

Manage Configuration Settings 

To define your own configuration settings in a bundle, use the Oro\Bundle\ConfigBundle\DependencyInjection\SettingsBuilder in the Configuration class:

src/Acme/Bundle/DemoBundle/DependencyInjection/Configuration.php 
namespace Acme\Bundle\DemoBundle\DependencyInjection;

use Oro\Bundle\ConfigBundle\DependencyInjection\SettingsBuilder;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    /**
     * @inheritDoc
     */
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('acme_demo');

        // provide your regular Symfony configuration here

        SettingsBuilder::append($treeBuilder->getRootNode(), [
            'foo' => [
                'value' => true,
                'type' => 'boolean',
            ],
            'bar' => [
                'value' => 10,
            ],
        ]);

        return $treeBuilder;
    }
}

The SettingsBuilder class is a helper class that adds additional nodes to the configuration tree. It expects the root node of the tree to which the new nodes are appended. The second argument is an array of configuration settings. The example above adds two options: foo and bar. Each option can get a default value and a type (one of scalar, boolean or array). The default type, if none is specified, is scalar.

Service container parameters can be used as the default value or service that implements Oro\Bundle\ConfigBundle\Provider\Value\ValueProviderInterface. All that is required is interface implementation without any additional setup. You can find a practical implementation of the interface in the ConfigBundle.

Example:

SettingsBuilder::append($root, [
    'locale' => [
        'value' => '%oro_locale.language%',
    ],
    'entity_segment_id' => [
        'value' => '@oro_config.provider.default_segment_id',
    ],
]);

After the tree is processed in the Extension class, pass configuration data to the container, and set an array with settings using the Containerbuilder#prependExtensionConfiguration method.

Example:

public function load(array $configs, ContainerBuilder $container): void
{
    // ....
    $container->prependExtensionConfig($this->getAlias(), SettingsBuilder::getSettings($config));
    // ...
}

See also

If you are not familiar with creating Configuration classes, check out an article on semantic configurations in the official documentation.

Note

How the SettingsBuilder Works

Internally, the settings builder creates a new tree with settings being the root node. For each configuration option passed to the append() method, a new node is created and appended to the internal tree.

Finally, the complete tree is appended to the node that was passed to append().

Change Config Value via Console Command 

You can change the config parameter value in the global scope via the console command oro:config:update.

This command has two arguments:

  • Config parameter name - the key of the config parameter you want to change. For example, ‘oro_anybundle.anysetting’;

  • Config parameter value - the value you want to set to the parameter.

For example, to update the back-office and storefront URLs of an OroCommerce instance respectively, run:

php bin/console oro:config:update oro_ui.application_url 'http://admin.example.com'
php bin/console oro:config:update oro_website.url 'http://store.example.com'
php bin/console oro:config:update oro_website.secure_url 'https://store.example.com'

Create Configuration Forms 

To enable a user to modify their configuration settings, create a form to be presented to the user. The form configuration is done in the system_configuration.yml file of the bundle.

Fields 

For each option, define a field under the fields key:

Acme/Bundle/DemoBundle/Resources/config/oro/system_configuration.yml 
system_configuration:
    fields:
        foo:
            type: checkbox
            options:
                label: "A label"
            priority: 10
        bar:
            type: text
            priority: 20
            tooltip: "A tooltip"

The only required field is type, which can refer to any valid form type.

Other supported fields are:

Field

Description

type

The form type (required)

options

Additional options that are passed to the form type

tooltip

A tooltip containing additional information

acl_resource

ACL resource the user needs to be allowed to change the option

priority

Optional field display order

Access Configuration Values 

In Controllers 

To retrieve configuration values inside a controller, use the oro_config.manager service which is an instance of Oro\ConfigBundle\Config\ConfigManager. Use its get() method to retrieve the value of a setting:

src/Acme/Bundle/DemoBundle/Controller/DemoController.php 
namespace Acme\Bundle\DemoBundle\Controller;

use Oro\Bundle\ConfigBundle\Config\ConfigManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class DemoController extends AbstractController
{
    private ConfigManager $configManager;

    public function __constructor(ConfigManager $configManager)
    {
        $this->configManager = $configManager;
    }

    public function demoAction()
    {
        $foo = $this->configManager->get('acme_demo.foo');

        // ...
    }
}

Note

The actual setting name is to be prefixed by the bundle alias (acme_demo for AcmeDemoBundle).

In Templates 

In a Twig template, use the oro_config_value() helper to retrieve the value of a configuration option:

{# setting becomes the value the user configured or true if they didn't #}
{% set setting = oro_config_value('acme_demo.foo') %}

Note

The actual setting name is to be prefixed by the bundle alias (here acme_demo for AcmeDemoBundle).

In Workflows and Operations (Actions) 

In workflows, you can use a condition to check that System Configuration has the necessary value.

Class: Oro\Bundle\ConfigBundle\Condition\IsSystemConfigEqual

Alias: is_system_config_equal

Description: Check that System Configuration has the necessary value

Parameters:

  • key - configuration key of stored value;

  • value - compared value;

Configuration Example

'@is_system_config_equal': ['some_config_path', 'needed value']

Add a New Configuration Scope 

To add a new config scope:

  1. Add scope manager.

    A scope manager is a class that provides access to configuration attributes in a particular scope. This class should extend AbstractScopeManager.

    In the simplest case, scope manager looks like this:

    namespace Acme\Bundle\DemoBundle\Config;
    
    use Oro\Bundle\ConfigBundle\Config\AbstractScopeManager;
    
    class TestScopeManager extends AbstractScopeManager
    {
        /**
         * {@inheritDoc}
         */
        public function getScopedEntityName(): string
        {
            return 'test'; // scope entity name
        }
    
        /**
         * {@inheritDoc}
         */
        public function getScopeId(): int
        {
            return 0; // scope entity id (can be different for different cases)
        }
    
        /**
         * {@inheritDoc}
         */
        public function setScopeId(?int $scopeId): void
        {
        }
    
        /**
         * {@inheritDoc}
         */
        protected function isSupportedScopeEntity(object $entity): bool
        {
            return false;
        }
    
        /**
         * {@inheritDoc}
         */
        protected function getScopeEntityIdValue(object $entity): int
        {
            throw new \LogicException(sprintf('"%s" is not supported.', ClassUtils::getClass($entity)));
        }
    }
    

    This manager should be registered as the service with tag oro_config.scope:

        services:
            acme_demo.scope.test:
                class: Acme\Bundle\DemoBundle\Config\TestScopeManager
                public: false
                parent: oro_config.scope_manager.abstract
                tags:
                    - { name: oro_config.scope, scope: test, priority: 50 }
    
    After this, the scope `test` will be used when retrieving a config value. This scope will be between the `global` and `user` scopes.
    You can use this scope with the `oro_config.test` config provider.
    
  2. Change scope values via the UI.

    To be able to change values for a new scope, add a new tree structure for this scope in the system_configuration.yml file, for example:

    system_configuration:
       tree:
          test_configuration:
              platform:
                  children:
                      general_setup:
                          children:
                              localization:
                                  priority: 255
                                  children:
                                      locale_settings:
                                          priority: 100
                                          children:
                                              - oro_locale.locale
    ...
    

    In this example, a user is allowed to change the locale settings in the test scope.

    Next, add a new form provider for the test scope:

    namespace Acme\Bundle\DemoBundle\Provider;
    
    use Oro\Bundle\ConfigBundle\Provider\AbstractProvider;
    
    class TestConfigurationFormProvider extends AbstractProvider
    {
        protected const TEST_TREE_NAME = 'test_configuration';
    
        /**
         * @inheritDoc
         */
        protected function getTreeName(): string
        {
            return $this->getTreeData(self::TEST_TREE_NAME, self::CORRECT_FIELDS_NESTING_LEVEL);
        }
    
        /**
         * @inheritDoc
         */
        protected function getParentCheckboxLabel(): string
        {
            return $this->getJsTreeData(self::TEST_TREE_NAME, self::CORRECT_MENU_NESTING_LEVEL);
        }
    }
    

    register it as a service:

    services:
        acme_demo.provider.form_provider.test:
            class: Acme\Bundle\DemoBundle\Provider\TestConfigurationFormProvider
            parent: oro_config.provider.abstract_provider
            lazy: true
    

    add a new action to manipulate data:

    #[Route(
        path: '/test-config-route/{activeGroup}/{activeSubGroup}',
        name: 'test_config',
        requirements: ['id' => '\d+'],
        defaults: ['activeGroup' => null, 'activeSubGroup' => null]
    )]
    #[Template]
    public function testConfigAction(Request $request, $activeGroup = null, $activeSubGroup = null)
    {
        $provider = $this->get('acme_demo.provider.form_provider.test');
    
        list($activeGroup, $activeSubGroup) = $provider->chooseActiveGroups($activeGroup, $activeSubGroup);
    
        $tree = $provider->getJsTree();
        $form = false;
    
        if ($activeSubGroup !== null) {
            $form = $provider->getForm($activeSubGroup);
    
            $manager = $this->get('oro_config.test');
    
            if ($this->get('oro_config.form.handler.config')
                ->setConfigManager($manager)
                ->process($form, $request)
            ) {
                $request->getSession()->getFlashBag()->add(
                    'success',
                    $this->get('translator')->trans('oro.config.controller.config.saved.message')
                );
    
                // outdated content tags, it's only special case for generation that is not covered by NavigationBundle
                $taggableData = ['name' => 'organization_configuration', 'params' => [$activeGroup, $activeSubGroup]];
                $tagGenerator = $this->get('oro_sync.content.tag_generator');
                $sender       = $this->get('oro_sync.content.data_update_topic_sender');
    
                $sender->send($tagGenerator->generate($taggableData));
            }
        }
    
        return [
            'data'           => $tree,
            'form'           => $form ? $form->createView() : null,
            'activeGroup'    => $activeGroup,
            'activeSubGroup' => $activeSubGroup,
        ];
    }
    

    and the template:

    {% extends '@OroConfig/configPage.html.twig' %}
    {% import '@OroUI/macros.html.twig' as UI %}
    
    {% set pageTitle = [
            'acme_test.some_label'|trans
        ]
    %}
    
    {% set formAction    = path(
            'test_config',
            {activeGroup: activeGroup, activeSubGroup: activeSubGroup}
        )
    %}
    {% set routeName = 'test_config' %}
    

Behat 

To enable one or several configuration options in behat:

Given I enable configuration options:
| oro_config.some_option  |
| oro_config.some_option2 |

Configuration Form Definition 

The configuration should be placed into the Resources/config/oro/system_configuration.yml file in any bundle.

The root node should be system_configuration.

Available Nodes 

  • groups - definition of field groups.

  • fields - definition of field (form type).

  • tree - definition of configuration form tree.

  • api_tree - definition of configuration items available through the API.

Groups 

This node should also be declared under the root node and contain an array of available field groups with their properties. A group is an abstract field bag, view representation of a group is managed on the template level of a specific configuration template and depends on its position in the tree.

This means that a group could be rendered as a fieldset, a tab, or part of an accordion list.

system_configuration:
    groups:
        platform: #unique name
            title: 'Platform'             # title is required
            icon:  fa-hdd-o
            priority: 30                  # sort order
            description: some description # add description on the next line after group header
            tooltip: some tooltip         # add a tooltip on the same line after group header
            page_reload: false            # if true, the page will be reloaded after saving if something changed in the group

Groups’ definitions will be replaced recursively from configs that will be parsed after the original definition. To override an existing group title, redefine the group with the same name and title value.

system_configuration:
    groups:
        platform:
            title: 'New title' # overridden title

To customize a group configuration form without implementing its own form type, use the configurator option. The configurator can be implemented as a static method or service. The signature of the configurator must be function (FormBuilderInterface $builder, array $options).

To specify a configurator, use the following syntax:

  • ClassName::methodName for a static method

  • @service_id::methodName for a method in a service

Please note that a group configuration form can have several configurators, which can be specified in different bundles.

Example

system_configuration:
    groups:
        # string syntax
        some_group:
            configurator: Acme\Bundle\DemoBundle\Form\Configurator\SettingsFormConfigurator::buildForm
        # array syntax
        some_group:
            configurator:
                - Acme\Bundle\DemoBundle\Form\Configurator\SettingsFormConfigurator::buildForm
                - '@acme.settings_form_configurator::buildForm'
namespace Acme\Bundle\DemoBundle\Form\Configurator;

use Symfony\Component\Form\FormBuilderInterface;

class SettingsFormConfigurator
{
    public static function buildForm(FormBuilderInterface $builder, array $options)
    {
        // put your configuration code here
    }
}

To customize the handling of a group configuration form, use the handler option. The handler can be implemented as a static method or a service. The signature of the handler must be function (ConfigManager $manager, ConfigChangeSet $changeSet, Form $form).

To specify a handler, use the following syntax:

  • ClassName::methodName for a static method

  • @service_id::methodName for a method in a service

Please note that a group configuration form can have several handlers, and they can be specified in different bundles. All handlers are executed only if a group configuration form does not have validation errors and the changed configuration option is saved. See ConfigHandler for details.

Example

system_configuration:
    groups:
        # string syntax
        some_group:
            handler: Acme\Bundle\DemoBundle\Form\Handler\SettingsFormHandler::handle
        # array syntax
        some_group:
            handler:
                - Acme\Bundle\DemoBundle\Form\Handler\SettingsFormHandler::handle
                - '@acme.settings_form_handler::handle'
namespace Acme\Bundle\DemoBundle\Form\Handler;

use Oro\Bundle\ConfigBundle\Config\ConfigChangeSet;
use Oro\Bundle\ConfigBundle\Config\ConfigManager;

class SettingsFormHandler
{
    public static function handle(ConfigManager $manager, ConfigChangeSet $changeSet)
    {
        // put your additional form handling code here
    }
}

Fields 

Field declaration requires property type.

  • data_type - must be specified for all fields except ui_only ones

  • type - refers to the form type of the field that should be created

  • search_type - indicates how to search by field value, read more in the [Search Type Provider](#search-type-provider) section

  • tooltip - show additional info about field

  • acl_resource - determines acl resource to check permissions to change config field value(optional)

  • priority - sort order for displaying(optional)

  • ui_only - indicates whether a field is used only in the UI and related to any variable (optional, defaults to false)

  • property_path - overrides configuration key where the field’s value will be stored (by default, the field’s name is used as path)

Property options is also available; it is a proxy to form type definition.

Example

system_configuration:
    fields:
        date_format:
            data_type: string
            type: text # can be any custom type
            search_type: text
            options:
               label: 'Date format'
               tooltip: 'Some additional information'
               resettable: false  # should "use default checkbox" be shown(optional, default: true)
               # here we can override any default option of the given form type
               # also here can be added field tooltips
            acl_resource: 'acl_resource_name'
            priority: 20
            page_reload: false # if true, the page will be reloaded after saving if the field changed

Tree 

Configuration of the form tree defines the nested form elements. The tree name should be unique to prevent content from merging from other trees. All nested elements of the group should be placed under the “child” node. The sort order can be set with the “priority” property.

Example

system_configuration:
    tree:
        tree_name:
            group1:
                priority: 20
                children:
                    some_group2:
                        children:
                            some_group3:
                                - some_field
                                ...
                                - some_another_field
API Tree 

The api_tree section is used to define which configuration option should be available through the API, for example REST API. It can also be used to split the options into logical groups. Using the group name, an API client can get only a subset of the options.

Please note that

  • A configuration option must be defined in the fields section and have a data_type attribute.

  • Nested groups are allowed. The nesting level is not limited.

Example

system_configuration:
    api_tree:
        look-and-feel:                               # group name
            oro_entity_pagination.enabled: ~         # configuration option
        sync:                                        # group name
            contacts:                                # nested group name
                acme_sync.contacts_enabled: ~        # configuration option
                acme_sync.contacts_sync_direction: ~
            tasks:
                acme_sync.tasks_enabled: ~

Search Type Provider 

You can add your own rules on how system configuration search should work. By default, search works:

Define a Search Provider 

Create your own DemoSearchProvider that implements SearchProviderInterface.

namespace Acme\Bundle\DemoBundle\Provider;

use Oro\Bundle\ConfigBundle\Config\ConfigBag;
use Oro\Bundle\ConfigBundle\Provider\SearchProviderInterface;

class DemoSearchProvider implements SearchProviderInterface
{
    private ConfigBag $configBag;

    /**
     * @param ConfigBag $configBag
     */
    public function __construct(ConfigBag $configBag)
    {
        // use config bag to obtain group or field configuration data
        $this->configBag = $configBag;
    }

    /**
     * @inheritDoc
     */
    public function supports($name): bool
    {
        // example of how the field can be determined
        return $this->configBag->getFieldsRoot($name) !== false;
    }

    /**
     * @inheritDoc
     */
    public function getData($name): array
    {
        // example how to filter by `search_type`
        $field = $this->configBag->getFieldsRoot($name);
        if ($field['search_type'] === 'your_own_search_type') {
            // return your own search data for current field
        }

        return [];
    }
}

Register your search provider as a service in the DI container with the oro_config.configuration_search_provider tag:

services:
    acme_demo.configuration_search_provider.demo:
        class: Acme\Bundle\DemoBundle\Provider\DemoSearchProvider
        public: false
        arguments:
            - '@oro_config.config_bag'
        tags:
            - { name: oro_config.configuration_search_provider, priority: -20 }

Business Tip

Major digital transformation is taking place in industries like manufacturing. Learn more about the benefits of digital transformation in manufacturing industry.