Important

You are browsing the documentation for version 4.1 of OroCommerce, OroCRM and OroPlatform, which is no longer maintained. Read version 5.1 (the latest LTS version) of the Oro documentation to get up-to-date information.

See our Release Process documentation for more information on the currently supported and upcoming releases.

Feature Toggle

Define a New Feature

Features are defined in configuration files placed into Resources/config/oro/features.yml.

Each feature consists of one required option, the label. You can configure the following sections, out-of-the-box:

  • label - a feature title

  • description - a feature description

  • toggle - a system configuration option key that is used as a feature toggle

  • dependencies - a list of feature names that the current feature depends on

  • routes - a list of route names

  • configuration - a list of system configuration groups and fields

  • workflows - a list of workflow names

  • processes - a list of process names

  • operations - a list of operation names

  • api_resources - a list of entity FQCNs that are available as API resources

  • commands - a list of commands which depend on the feature. Running these commands is impossible or is not reasonable when the feature is disabled

  • entities - a list of entity FQCNs

  • field_configs - a list of field names

  • dashboard_widgets - a list of dashboard widget names

  • cron_jobs - a list of cron job names

  • navigation_items - a list of navigation items

  • placeholder_items - a list of placeholder item names

  • mq_topics - a list of MQ topic names

Example of the features.yml configuration

 1features:
 2    acme:
 3        label: acme.feature.label
 4        description: acme.feature.description
 5        toggle: acme.feature_enabled
 6        dependencies:
 7            - foo
 8            - bar
 9        routes:
10            - acme_entity_view
11            - acme_entity_create
12        configuration:
13            - acme_general_section
14            - acme.some_option
15        workflows:
16            - acme_sales_flow
17        processes:
18            - acme_some_process
19        operations:
20            - acme_some_operation
21        api_resources:
22            - Acme\Bundle\Entity\Page
23        commands:
24            - oro:search:index
25        entities:
26            - Acme\Bundle\Entity\Page
27        field_configs:
28            - 'some_field_name'
29        dashboard_widgets:
30            - 'page_dashboard_widget'
31        cron_jobs:
32            - 'acme:cron:sync-job'
33        navigation_items:
34            - 'application_menu.sales_tab.acme_order_list'
35        placeholder_items:
36            - acme_create_page_button
37        mq_topics:
38            - 'acme.mq_topics.calculate'

Add New Options to Feature Configuration

Feature configuration may be extended with new configuration options. To add a new configuration option, you need to add a feature configuration that implements ConfigurationExtensionInterface and register it with the oro_feature.config_extension tag. For example, there are some Acme processors which should be configured with the acme_processor option.

Configuration extension:

 1<?php
 2
 3namespace Acme\Bundle\ProcessorBundle\Config;
 4
 5use Oro\Bundle\FeatureToggleBundle\Configuration\ConfigurationExtensionInterface;
 6use Symfony\Component\Config\Definition\Builder\NodeBuilder;
 7
 8class FeatureConfigurationExtension implements ConfigurationExtensionInterface
 9{
10    /**
11     * {@inheritdoc}
12     */
13    public function extendConfigurationTree(NodeBuilder $node)
14    {
15        $node
16            ->arrayNode('acme_processor')
17                ->prototype('variable')
18                ->end()
19            ->end();
20    }
21}

Extension registration:

1services:
2    acme.configuration.feature_configuration_extension:
3        class: Acme\Bundle\ProcessorBundle\Config\FeatureConfigurationExtension
4        tags:
5            - { name: oro_feature.config_extension }

Check Feature State

Feature state is determined by FeatureChecker. There are proxy classes that expose a feature check functionality to layout updates, operations, workflows, processes, and twig.

Feature state is resolved by isFeatureEnabled($featureName, $scopeIdentifier = null)

Feature resource types are nodes of feature configuration (routes, workflows, configuration, processes, operations, api_resources), resources are their values. Resource is disabled if it is included into at least one disabled feature. Resource state is resolved by public function isResourceEnabled($resource, $resourceType, $scopeIdentifier = null)

Layout Updates

  • Check the feature state =data[‘feature’].isFeatureEnabled(‘feature_name’)

  • Check the resource state =data[‘feature’].isResourceEnabled(‘acme_product_view’, ‘routes’)

Set the block visibility based on the feature state:

1layout:
2    actions:
3        - '@add':
4            id: products
5            parentId: page_content
6            blockType: datagrid
7            options:
8                grid_name: products-grid
9                visible: '=data["feature"].isFeatureEnabled("product_feature")'

Processes, Workflows, Operations

In processes, workflows and operations, config expressions may be used to check the feature state

  • Check the feature state

    1'@feature_enabled':
    2    feature: 'feature_name'
    3    scope_identifier: $.scopeIdentifier
    
  • Check the resource state

    1'@feature_resource_enabled':
    2    resource: 'some_route'
    3    resource_type: 'routes'
    4    scope_identifier: $.scopeId
    

Twig

  • Check the feature state feature_enabled($featureName, $scopeIdentifier = null)

  • Check the resource state feature_resource_enabled($resource, $resourceType, $scopeIdentifier = null)

Include a Service Into a Feature

Any service that requires a feature functionality, needs to implement the FeatureToggleableInterface interface. All checks are done by developer.

OroFeatureToggleBundle provides helper functionality to inject a feature checker and a feature name into services marked with the oro_featuretogle.feature tag. FeatureCheckerHolderTrait contains implementation of methods from FeatureToggleableInterface.

Some extensions can extend the form, and we need to include this extension functionality into a feature. In this case, FeatureChecker should be injected into service, and feature availability should be checked where needed.

Extension:

 1<?php
 2
 3namespace Acme\Bundle\CategoryBundle\Form\Extension;
 4
 5use Symfony\Component\Form\AbstractTypeExtension;
 6use Symfony\Component\Form\FormBuilderInterface;
 7
 8use Oro\Bundle\FeatureToggleBundle\Checker\FeatureToggleableInterface;
 9use Oro\Bundle\FeatureToggleBundle\Checker\FeatureCheckerHolderTrait;
10
11class ProductFormExtension extends AbstractTypeExtension implements FeatureToggleableInterface
12{
13    use FeatureCheckerHolderTrait;
14
15    /**
16     * {@inheritdoc}
17     */
18    public static function getExtendedTypes(): iterable
19    {
20        return ['acme_product'];
21    }
22
23    /**
24     * {@inheritdoc}
25     */
26    public function buildForm(FormBuilderInterface $builder, array $options)
27    {
28        if (!$this->isFeaturesEnabled()) {
29            return;
30        }
31
32        $builder->add(
33            'category',
34            'acme_category_tree',
35            [
36                'required' => false,
37                'mapped' => false,
38                'label' => 'Category'
39            ]
40        );
41    }
42}

Extension registration:

1services:
2    acme_category.form.extension.product_form:
3        class: Acme\Bundle\CategoryBundle\Form\Extension\ProductFormExtension
4    tags:
5        - { name: oro_featuretogle.feature, feature: acme_feature }

Check Feature State with a Feature Voter

Feature state is checked by feature voters. All voters are called each time you use the isFeatureEnabled() or isResourceEnabled() method on the feature checker. The feature checker makes the decision based on the configured strategy defined in the system configuration or per feature, which can be: affirmative, consensus, or unanimous.

By default, ConfigVoter is registered to check features availability. It checks the feature state based on the value of a toggle option defined in the features.yml configuration.

A custom voter needs to implement Oro\Bundle\FeatureToggleBundle\Checker\Voter\VoterInterface. Imagine that we have the state checker that returns decision based on a feature name and a scope identifier. The feature is enabled for the valid state and disabled for the invalid state. In other cases, do not vote.

Such voter looks as follows:

 1<?php
 2
 3namespace Acme\Bundle\ProcessorBundle\Voter;
 4
 5use Oro\Bundle\FeatureToggleBundle\Checker\Voter\VoterInterface;
 6
 7class FeatureVoter implements VoterInterface
 8{
 9    /**
10     * @var StateChecker
11     */
12    private $stateChecker;
13
14    /**
15     * @param StateChecker $stateChecker
16     */
17    public function __construct(StateChecker $stateChecker) {
18        $this->stateChecker = $stateChecker;
19    }
20
21    /**
22     * @param string $feature
23     * @param object|int|null $scopeIdentifier
24     * return int either FEATURE_ENABLED, FEATURE_ABSTAIN, or FEATURE_DISABLED
25     */
26    public function vote($feature, $scopeIdentifier = null)
27    {
28        if ($this->stateChecker($feature, $scopeIdentifier) === StateChecker::VALID_STATE) {
29            return self::FEATURE_ENABLED;
30        }
31        if ($this->stateChecker($feature, $scopeIdentifier) === StateChecker::INVALID_STATE) {
32            return self::FEATURE_DISABLED;
33        }
34
35        return self::FEATURE_ABSTAIN;
36    }
37}

Now, configure a voter:

1services:
2    acme_process.voter.feature_voter:
3        class: Acme\Bundle\ProcessorBundle\Voter\FeatureVoter
4        arguments: [ '@acme_process.voter.state_checker' ]
5        tags:
6            - { name: oro_featuretogle.voter }

Change Decision Strategy

There are three strategies available:

  • affirmative – The strategy grants access if one voter grants access;

  • consensus – The strategy grants access if there are more voters that grant access than those that deny;

  • unanimous (default) – The strategy grants access only if all voters grant access.

Strategy configuration (may be defined in Resources/config/oro/app.yml)

1oro_featuretoggle:
2    strategy: affirmative
3    allow_if_all_abstain: true
4    allow_if_equal_granted_denied: false

or in feature definition

1features:
2    acme:
3        label: acme.feature.label
4        strategy: affirmative
5        allow_if_all_abstain: true
6        allow_if_equal_granted_denied: false

Use Checker for Commands

Commands launched as subcommands cannot be skipped globally. To avoid running such commands, add an implementation of FeatureCheckerAwareInterface to your parent command, import FeatureCheckerHolderTrait (via use FeatureCheckerHolderTrait;), and check the feature status via featureChecker that is automatically injected into your command.

 1<?php
 2
 3namespace Acme\Bundle\FixtureBundle\Command;
 4
 5use Oro\Bundle\FeatureToggleBundle\Checker\FeatureCheckerHolderTrait;
 6use Oro\Bundle\FeatureToggleBundle\Checker\FeatureCheckerAwareInterface;
 7
 8class LoadDataFixturesCommand implements FeatureCheckerAwareInterface
 9{
10
11    use FeatureCheckerHolderTrait;
12
13    protected function execute(InputInterface $input, OutputInterface $output)
14    {
15        $commands = [
16            'oro:cron:analytic:calculate' => [],
17            'oro:b2b:lifetime:recalculate'          => ['--force' => true]
18        ];
19
20        foreach ($commands as $commandName => $options) {
21            if ($this->featureChecker->isResourceEnabled($commandName, 'commands')) {
22                $command = $this->getApplication()->find($commandName);
23                $input = new ArrayInput(array_merge(['command' => $commandName], $options));
24                $command->run($input, $output);
25            }
26        }
27    }
28}