Skip to main content
Version: Next

Custom Rule Definition

Add a custom rule definition in two steps:

  1. Create a PHP class for the rule definition.
  2. Register the class as a service.

Create a Custom Rule Definition PHP Class​

The following example adds a Sample Check rule definition. A rule definition class extends AbstractRuleDefinition:

<?php
declare(strict_types=1);

namespace Foo\Bar;

use Pimcore\Bundle\DataQualityManagementBundle\Installer;
use Pimcore\Bundle\DataQualityManagementBundle\Model\RuleDefinition\AbstractRuleDefinition;
use Pimcore\Bundle\DataQualityManagementBundle\Model\ValidatorResponse;
use Pimcore\Model\DataObject;
use Pimcore\Model\DataObject\Concrete;
use Symfony\Contracts\Translation\TranslatorInterface;

class SampleCheck extends AbstractRuleDefinition
{
protected const FIELD_TYPE = 'sampleCheck';

public function __construct(
private readonly TranslatorInterface $translator
) {
parent::__construct($this->translator);
}

public function getType(): string
{
return self::FIELD_TYPE;
}

public function supports(string $type): bool
{
return $type === $this->getType();
}

public function getSuggestion(
array $rule, //the rule definition
string $locale,
Concrete $object, //the object the score was calculated for
array $ruleData //the data stored in the Data Object which are related to the current rule
): ?string
{
//return a string suggestion to display in the UI:
if (!isset($rule['yourSuggestionField'])) {
return null;
}

return $this->translator->trans(
$rule['yourSuggestionField'],
[],
Installer::TRANSLATION_DOMAIN,
$locale
);
}

public function forceRecalculation(): bool
{
return false;
//if true:
//the rule definition is recalculated for every saved Data Object (and its children)
//on every class definition save, even when the data has not changed.
//else:
//the rule definition is recalculated for every saved Data Object (and its children)
//only when its configuration changed (the same behavior as the Recommended Fields check).
}

public function calculateScore(DataObject\Concrete $dataObject, array $ruleDefinition): ValidatorResponse
{
//your logic here...
return new ValidatorResponse(
true, //true if the rule definition is valid, false otherwise
[], //array of invalid fields
[] //array of additional data (to be used for example in the suggestion)
);
}
}
info

The FIELD_TYPE constant is the value stored in the class definition's definitions array as type; see Configuring the rule in a class definition.

Building the suggestion from rule data​

$ruleData holds the stored result of the last calculateScore() run for this rule, with the keys valid, invalidFields and messageProperties taken from the returned ValidatorResponse. Use it to append details to the translated message instead of returning the translation as is:

public function getSuggestion(
array $rule,
string $locale,
Concrete $object,
array $ruleData
): ?string
{
$message = $this->translator->trans(
'plugin_pimcore_dataqualitymanagement_fixed_suggestion',
[],
Installer::TRANSLATION_DOMAIN,
$locale
);

if (!empty($ruleData['invalidFields'])) {
// one invalid field per line, the Studio data quality tab preserves the line breaks
$message .= "\n" . implode("\n", $ruleData['invalidFields']);
}

return $message;
}

AbstractRuleDefinition already implements this behavior: its default getSuggestion() returns null when invalidFields is empty, and otherwise appends the invalid field names, one per line, to the plugin_pimcore_dataqualitymanagement_fixed_suggestion translation. Override the method only when the suggestion needs different wording or different data.

Register the New Class as a Service​

Register the class as a service and tag it as pimcore.data_quality_management.rule_definition:

    ...

Foo\Bar\SampleCheck:
tags:
- { name: pimcore.data_quality_management.rule_definition }

...

Once tagged, the rule definition participates in score calculation like any built-in rule: it is available to bin/console dqm:score:recalculate, honors forceRecalculation(), and its getSuggestion() output appears in the Data Object editor's Data Quality Details tab.

Configuring the Rule in a Class Definition​

A rule definition is configured per Data Quality field in the class editor. Each configured rule is stored as a plain array in the field's definitions array and read by key:

  • type - resolves the rule definition via the service tagged with a matching getType() (FIELD_TYPE above).
  • title and weight - rendered by the class editor for every rule type. weight is validated as an integer between 1 and 100 for every entry, regardless of the other keys.
  • fieldsToCheck - an array of key names, from this same entry, that must be present and non-empty for the class definition to save. The class editor always includes title and weight.
  • fieldsToTranslate - an array of key names whose values are registered as translation keys in the dataQualityManagement domain when the class definition is saved. Defaults to title.
  • forceRecalculation - stored copy of the rule definition's forceRecalculation() flag.
  • any additional keys the custom calculateScore() and getSuggestion() implementation expects (yourSuggestionField in the example above).

Adding a Settings Form to the Class Editor​

Pimcore Studio's class editor renders the settings of each rule type from a rule definition registry. The bundle registers the three built-in rule types there and binds the registry in the shared Studio UI dependency injection container under the service id DataQualityManagement/ClassEditor/RuleDefinitionRegistry. Register your own rule type from the Studio UI plugin of your bundle to make it selectable in the Rule Definitions field collection and to render its settings.

info

Your bundle needs a Studio UI plugin to run frontend code. See the Pimcore Studio UI documentation and the studio-example-bundle for the build setup.

A registry item consists of:

KeyRequiredDescription
typeyesThe rule type; must equal getType() of the PHP rule definition (sampleCheck above).
translationKeyyesTranslation key of the label shown in the Add Rule select. Add it to the studio.<locale>.yaml files of your bundle.
componentnoReact element with the rule specific settings: Form.Item elements whose name is the key stored in the rule definition. title and weight are added by the bundle. Omit it for rules without settings of their own.
fieldsToChecknoKeys of component that must be filled for the class definition to save. title and weight are always included.
fieldsToTranslatenoKeys whose values are registered as translation keys on save. Defaults to ['title'].
forceRecalculationnoStored forceRecalculation flag, defaults to false.

Registering a type that is already registered replaces its form; this also allows replacing the form of a built-in rule type.

1. Create the settings component​

The component only renders the rule specific fields. Use the Form and input components exported by the Studio UI SDK so the fields integrate with the class editor form:

// assets/js/src/data-quality/sample-check-rule.tsx
import React from 'react'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
import { Form, Input, InputNumber } from '@pimcore/studio-ui-bundle/components'

export const SampleCheckRule = (): React.JSX.Element => {
const { t } = useTranslation()

return (
<>
<Form.Item
label={ t('app.sample-check.threshold') }
name="threshold"
required
>
<InputNumber min={ 0 } />
</Form.Item>
<Form.Item
label={ t('app.sample-check.suggestion') }
name="yourSuggestionField"
required
>
<Input />
</Form.Item>
</>
)
}

The field names become keys of the stored rule definition and are passed to calculateScore() and getSuggestion() as part of $ruleDefinition / $rule.

2. Register the rule type in a module​

Resolve the registry from the container and register the rule type in the onInit of a module. Modules run after all plugins have bound their services, so the registry is guaranteed to exist at that point.

// assets/js/src/data-quality/sample-check-module.tsx
import React from 'react'
import { container, type AbstractModule } from '@pimcore/studio-ui-bundle'
import { SampleCheckRule } from './sample-check-rule'

// Service id under which the Data Quality Management bundle binds its rule definition registry.
const RULE_DEFINITION_REGISTRY_SERVICE_ID = 'DataQualityManagement/ClassEditor/RuleDefinitionRegistry'

interface RuleDefinitionRegistry {
register: (item: {
type: string
translationKey: string
component?: React.JSX.Element
fieldsToCheck?: string[]
fieldsToTranslate?: string[]
forceRecalculation?: boolean
}) => void
}

export const SampleCheckModule: AbstractModule = {
onInit: (): void => {
if (!container.isBound(RULE_DEFINITION_REGISTRY_SERVICE_ID)) {
// the Data Quality Management bundle is not installed
return
}

const registry = container.get<RuleDefinitionRegistry>(RULE_DEFINITION_REGISTRY_SERVICE_ID)

registry.register({
type: 'sampleCheck',
translationKey: 'app.sample-check',
component: <SampleCheckRule />,
fieldsToCheck: ['threshold', 'yourSuggestionField'],
fieldsToTranslate: ['title', 'yourSuggestionField']
})
}
}
// assets/js/src/plugins.ts
import { type IAbstractPlugin } from '@pimcore/studio-ui-bundle'
import { SampleCheckModule } from './data-quality/sample-check-module'

export const SampleCheckPlugin: IAbstractPlugin = {
name: 'app-sample-check-plugin',

onStartup: ({ moduleSystem }): void => {
moduleSystem.registerModule(SampleCheckModule)
}
}

The registry contract is declared locally on purpose: the Data Quality Management bundle's frontend is loaded via module federation and only exposes its plugin, so your bundle needs neither a build- nor a runtime dependency on it.

Once registered, Sample Check appears in the Add Rule select of every Data Quality field and stores its configuration like the built-in rules:

'definitions' => [
[
'type' => 'sampleCheck',
'title' => 'Sample Check',
'weight' => 1,
'threshold' => 3,
'yourSuggestionField' => 'plugin_pimcore_dataqualitymanagement_custom_suggestion',
'fieldsToCheck' => ['title', 'weight', 'threshold', 'yourSuggestionField'],
'fieldsToTranslate' => ['title', 'yourSuggestionField'],
'forceRecalculation' => false,
],
// ...other rule definitions
],

Rules Without a Settings Form​

A custom rule definition also works without a class editor form: add its configuration directly to the Data Quality field's definitions array in the class definition, either by editing the class definition PHP export or by updating it programmatically. Provide fieldsToCheck even when it is empty; the validator iterates it directly.

'definitions' => [
[
'type' => 'sampleCheck',
'title' => 'Sample Check',
'weight' => 1,
'fieldsToCheck' => ['title', 'weight'],
'fieldsToTranslate' => ['title'],
'yourSuggestionField' => 'plugin_pimcore_dataqualitymanagement_custom_suggestion',
],
// ...other rule definitions
],

The class editor renders such a rule with a notice that no settings form is registered for its type. title and weight remain editable, and all other stored keys are kept unchanged when the class definition is saved.

caution

weight is validated for every rule definition, built-in or custom, and must be an integer between 1 and 100. title (and any other field) is enforced as mandatory only when it is listed in that entry's own fieldsToCheck.