class DefaultMatchingEngine in CRM Core 8
Same name and namespace in other branches
- 8.3 modules/crm_core_match/src/Plugin/crm_core_match/engine/DefaultMatchingEngine.php \Drupal\crm_core_match\Plugin\crm_core_match\engine\DefaultMatchingEngine
- 8.2 modules/crm_core_match/src/Plugin/crm_core_match/engine/DefaultMatchingEngine.php \Drupal\crm_core_match\Plugin\crm_core_match\engine\DefaultMatchingEngine
DefaultMatchingEngine class.
Extends CrmCoreMatchEngine to provide rules for identifying duplicate contacts.
Plugin annotation
@CrmCoreMatchEngine(
id = "default",
label = @Translation("Default Matching Engine"),
summary = @Translation("This is a simple matching engine from CRM Core. Allows administrators to specify matching rules for individual contact types on a field-by-field basis."),
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\crm_core_match\Plugin\crm_core_match\engine\MatchEngineBase implements ContainerFactoryPluginInterface, MatchEngineInterface
- class \Drupal\crm_core_match\Plugin\crm_core_match\engine\DefaultMatchingEngine
- class \Drupal\crm_core_match\Plugin\crm_core_match\engine\MatchEngineBase implements ContainerFactoryPluginInterface, MatchEngineInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of DefaultMatchingEngine
1 file declares its use of DefaultMatchingEngine
- DefaultEngineTest.php in modules/
crm_core_match/ tests/ src/ Unit/ DefaultEngineTest.php
File
- modules/
crm_core_match/ src/ Plugin/ crm_core_match/ engine/ DefaultMatchingEngine.php, line 25
Namespace
Drupal\crm_core_match\Plugin\crm_core_match\engineView source
class DefaultMatchingEngine extends MatchEngineBase {
const MATCH_CHARS_DEFAULT = 3;
/**
* The match field plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $pluginManager;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Constructs a default matching engine.
*/
public function __construct($configuration, $id, $definition, PluginManagerInterface $plugin_manager, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
parent::__construct($configuration, $id, $definition);
$this->pluginManager = $plugin_manager;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('plugin.manager.crm_core_match.match_field'), $container
->get('entity_type.manager'), $container
->get('entity_field.manager'));
}
/**
* {@inheritdoc}
*/
public function match(ContactInterface $contact) {
$ids = [];
$fields = $contact
->getFieldDefinitions();
$results = [];
$configuration_rules = $this
->getConfigurationItem('rules') ?: [];
foreach ($configuration_rules as $name => $rules) {
if (isset($fields[$name])) {
$rules['field'] = $fields[$name];
if (!$this->pluginManager
->hasDefinition($rules['field']
->getType())) {
continue;
}
/* @var \Drupal\crm_core_match\Plugin\crm_core_match\field\FieldHandlerInterface $field_handler */
$field_handler = $this->pluginManager
->createInstance($rules['field']
->getType(), $rules);
foreach ($field_handler
->getPropertyNames() as $name) {
$result = $field_handler
->match($contact, $name);
$keys = array_keys($result);
$key = reset($keys);
if (isset($results[$key])) {
if (isset($results[$key][$name])) {
$results[$key][$name] += $result[$key][$name];
}
else {
$results[$key] += $result[$key];
}
}
else {
$results += $result;
}
}
}
}
foreach ($results as $id => $rule_matches) {
$total_score = array_sum($rule_matches);
if ($total_score >= $this
->getConfigurationItem('threshold')) {
$ids[] = $id;
}
}
return $ids;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['threshold'] = [
'#type' => 'textfield',
'#title' => $this
->t('Threshold'),
'#description' => $this
->t('Defines the score at which a contact is considered a match.'),
'#maxlength' => 28,
'#size' => 28,
'#required' => TRUE,
'#default_value' => $this
->getConfigurationItem('threshold'),
];
$return_description = $this
->t(<<<EOF
If two or more contact records result in matches with identical scores, CRM Core
will give preference to one over the other base on selected option.
EOF
);
$form['return_order'] = [
'#type' => 'select',
'#title' => $this
->t('Return Order'),
'#description' => $return_description,
'#default_value' => $this
->getConfigurationItem('return_order'),
'#options' => [
'created' => $this
->t('Most recently created'),
'updated' => $this
->t('Most recently updated'),
'associated' => $this
->t('Associated with user'),
],
];
$strict_description = $this
->t(<<<EOF
Check this box to return a match for this contact type the first time one is
identified that meets the threshold. Stops redundant processing.
EOF
);
$form['strict'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Strict matching'),
'#description' => $strict_description,
'#default_value' => $this
->getConfigurationItem('strict'),
];
$form['rules'] = [
'#title' => $this
->t('Field Matching'),
'#type' => 'table',
'#tree' => TRUE,
'#header' => $this
->buildHeader(),
'#empty' => $this
->t('There are no fields available.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
'#theme_wrappers' => [
'form_element',
],
];
$form['unsupported'] = [
'#type' => 'details',
'#title' => $this
->t('Unsupported Fields'),
'#open' => TRUE,
// Hide table when empty. We set this to TRUE as soon there is at least
// one unsupported field.
'#access' => FALSE,
'table' => [
'#theme' => 'table',
'#header' => [
'label' => $this
->t('Name'),
'field_type' => $this
->t('Field type'),
],
'#rows' => [],
'#empty' => $this
->t('There are no unsupported fields.'),
],
'help_text' => [
'#theme_wrappers' => [
'container',
],
'#attributes' => [
'class' => [
'description',
],
],
'#markup' => $this
->t('A given field is unsupported when there is no field match handler for the field type it belongs to. In order to make it supported a handler plugin should be created for it.'),
],
];
// @todo: Display fields per bundle.
$contact_types = $this->entityTypeManager
->getStorage('crm_core_individual_type')
->loadMultiple();
$fields = [];
foreach ($contact_types as $contact_type_id => $value) {
$fields += $this->entityFieldManager
->getFieldDefinitions('crm_core_individual', $contact_type_id);
}
foreach ($fields as $field) {
$rules = $this
->getConfigurationItem('rules');
$config = empty($rules[$field
->getName()]) ? [] : $rules[$field
->getName()];
$config['field'] = $field;
if ($this->pluginManager
->hasDefinition($field
->getType())) {
$match_field_id = $field
->getType();
/* @var \Drupal\crm_core_match\Plugin\crm_core_match\field\FieldHandlerInterface $match_field */
$match_field = $this->pluginManager
->createInstance($match_field_id, $config);
foreach ($match_field
->getPropertyNames($field) as $name) {
$row = $this
->buildRow($match_field, $name, FALSE);
$form['rules'][$field
->getName() . ':' . $name] = $row;
}
}
else {
$form['unsupported']['table']['#rows'][] = [
'label' => $field
->getLabel(),
'type' => $field
->getType(),
];
$form['unsupported']['#access'] = TRUE;
}
}
return $form;
}
/**
* Builds the header row for the rule listing.
*
* @return array
* A render array structure of header strings.
*/
public function buildHeader() {
$header = [];
$header['status'] = $this
->t('Enabled');
$header['label'] = $this
->t('Name');
$header['field_type'] = $this
->t('Field type');
$header['operator'] = $this
->t('Operator');
$header['options'] = $this
->t('Options');
$header['score'] = $this
->t('Score');
$header['weight'] = $this
->t('Weight');
return $header;
}
/**
* Builds a row for an rule in the rule listing.
*
* @param \Drupal\crm_core_match\Plugin\crm_core_match\field\FieldHandlerInterface $field
* The match field of this rule.
* @param string $name
* The property name of this rule.
* @param bool $disabled
* Disables the form elements.
*
* @return array
* A render array structure of fields for this rule.
*/
public function buildRow(FieldHandlerInterface $field, $name, $disabled) {
$row = [];
$row['#attributes']['class'][] = 'draggable';
$row['#weight'] = $field
->getWeight($name);
$row['status'] = [
'#type' => 'checkbox',
'#default_value' => $field
->getStatus($name),
'#disabled' => $disabled,
];
$row['label'] = [
'#markup' => $field
->getLabel($name),
];
$row['type'] = [
'#markup' => $field
->getType(),
];
$row['operator'] = [
'#type' => 'select',
'#default_value' => $field
->getOperator($name),
'#empty_option' => !$disabled ? NULL : $this
->t('- Please Select -'),
'#options' => $field
->getOperators($name),
'#disabled' => $disabled,
];
$row['options'] = [
'#type' => 'textfield',
'#maxlength' => 28,
'#size' => 28,
'#default_value' => $field
->getOptions($name),
'#disabled' => $disabled,
];
$row['score'] = [
'#type' => 'textfield',
'#maxlength' => 4,
'#size' => 3,
'#default_value' => $field
->getScore($name),
'#disabled' => $disabled,
];
$row['weight'] = [
'#type' => 'weight',
'#title' => $this
->t('Weight for @field', [
'@field' => $field
->getLabel(),
]),
'#title_display' => 'invisible',
'#default_value' => $field
->getWeight($name),
'#attributes' => [
'class' => [
'weight',
],
],
];
return $row;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
if (!is_numeric($form_state
->getValue([
'configuration',
'threshold',
]))) {
$form_state
->setErrorByName('configuration[threshold]', $this
->t('Threshold must be a number.'));
}
$rules = $form_state
->getValue([
'configuration',
'rules',
]);
foreach ($rules as $field_name => $config) {
if ($config['status'] && empty($config['operator'])) {
$name = 'rules][' . $field_name . '][operator';
$message = $this
->t('You must select an operator for enabled field.');
$form_state
->setErrorByName($name, $message);
}
if (!is_numeric($config['score'])) {
$name = 'rules][' . $field_name . '][score';
$message = $this
->t('You must enter number in "Score" column.');
$form_state
->setErrorByName($name, $message);
}
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
// @todo: Build the same form and configuration structure.
$rules = [];
$this->configuration = $form_state
->getValue('configuration');
foreach ($form_state
->getValue([
'configuration',
'rules',
]) as $name => $config) {
if (strpos($name, ':') !== FALSE) {
list($parent, $child) = explode(':', $name, 2);
$rules[$parent][$child] = $config;
}
else {
$rules[$name] = $config;
}
}
$this->configuration['rules'] = $rules;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
// TODO: Implement defaultConfiguration() method.
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
// TODO: Implement calculateDependencies() method.
return [];
}
/**
* {@inheritdoc}
*/
public function getRules() {
$rules = [];
// Collect all fields of all contact types.
$contact_types = $this->entityTypeManager
->getStorage('crm_core_individual_type')
->loadMultiple();
$field_definitions = [];
foreach ($contact_types as $contact_type_id => $value) {
$field_definitions += $this->entityFieldManager
->getFieldDefinitions('crm_core_individual', $contact_type_id);
}
foreach ($this
->getConfigurationItem('rules') as $field_name => $field_settings) {
// Skip fields that got dropped and disabled on matcher.
if (!isset($field_definitions[$field_name]) || !$this
->isEnabled($field_settings)) {
continue;
}
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$field_definition = $field_definitions[$field_name];
$rules[$field_name] = [
'label' => $field_definition
->getLabel(),
'definition' => $field_definition,
];
}
return $rules;
}
/**
* A helper function to check if there are enabled field properties.
*
* @param array $properties
* An array of field properties.
*
* @return bool
* Returns TRUE if there is at least one enabled property. Otherwise, FALSE.
*/
protected function isEnabled(array $properties) {
return (bool) array_filter(array_map(function ($property) {
return $property['status'];
}, $properties));
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DefaultMatchingEngine:: |
protected | property | The entity field manager service. | |
DefaultMatchingEngine:: |
protected | property | The entity manager service. | |
DefaultMatchingEngine:: |
protected | property | The match field plugin manager. | |
DefaultMatchingEngine:: |
public | function |
Form constructor. Overrides MatchEngineBase:: |
|
DefaultMatchingEngine:: |
public | function | Builds the header row for the rule listing. | |
DefaultMatchingEngine:: |
public | function | Builds a row for an rule in the rule listing. | |
DefaultMatchingEngine:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
|
DefaultMatchingEngine:: |
public static | function |
Creates an instance of the plugin. Overrides MatchEngineBase:: |
|
DefaultMatchingEngine:: |
public | function |
Gets default configuration for this plugin. Overrides ConfigurablePluginInterface:: |
|
DefaultMatchingEngine:: |
public | function |
Gets the rules that are matched. Overrides MatchEngineBase:: |
|
DefaultMatchingEngine:: |
protected | function | A helper function to check if there are enabled field properties. | |
DefaultMatchingEngine:: |
public | function |
Finds matches for given contact. Overrides MatchEngineInterface:: |
|
DefaultMatchingEngine:: |
constant | |||
DefaultMatchingEngine:: |
public | function |
Form submission handler. Overrides MatchEngineBase:: |
|
DefaultMatchingEngine:: |
public | function |
Form validation handler. Overrides MatchEngineBase:: |
|
DefaultMatchingEngine:: |
public | function |
Constructs a default matching engine. Overrides MatchEngineBase:: |
|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
MatchEngineBase:: |
protected | property |
The engine configuration. Overrides PluginBase:: |
|
MatchEngineBase:: |
protected | property |
The plugin implementation definition. Overrides PluginBase:: |
|
MatchEngineBase:: |
protected | property |
The plugin id. Overrides PluginBase:: |
|
MatchEngineBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurablePluginInterface:: |
|
MatchEngineBase:: |
public | function |
Returns a specific item of this plugin's configuration. Overrides MatchEngineInterface:: |
|
MatchEngineBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurablePluginInterface:: |
|
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |