Creating Your Own Matching Engine

Last updated on
11 March 2017

CRM Core was designed to be very flexible when it comes to identifying duplicate contacts. In terms of how a duplicate is defined, the designers of CRM Core concerned themselves with use cases having duplicates living in the following spaces:

  • In CRM Core, as a contact
  • In CRM Core, as a contact of a different type
  • In Drupal, as defined in some other module
  • In some other database, accessible via a direct database connection
  • In some other system, accessible via web services

CRM Core Match was built to provide support for each of these, not directly, but through the use of matching engines. A matching engine is a tool used to identify duplicate contacts, wherever they may live, and according to whatever rules are important in your situation. It also allows developers to directly modify a contact record as part of the matching process. This can be very useful in situations where, for instance, CRM Core Match is being asked to identify duplicates in an external system and you need to keep a record of the external identifier for use in other situations.

It is strongly recommended that developers make use of matching engines as a tool for interacting with contacts. CRM Core Match provides a uniform way to apply matching criteria to contacts in a CRM Core installation. What this means is that, instead of having to write code that triggers in response to various actions in a Drupal site, you only need to have one set of logical operations that can be applied every time someone needs to identify a match. There are ample use cases that emerge when working with contacts where matching functionality needs to be applied outside of standard CRUD operations that are not supported by various hook systems in Drupal. CRM Core Match makes it simple to invoke matching logic in these situations without having to know what specifically is installed in a site, and to control the order in which matching logic is applied.

Building a Matching Engine

CRM Core Match provides support for building your own matching engines through the CrmCoreMatchEngine object. It has all the properties and methods needed to register itself with CRM Core Match and handle various matching operations that may come about.

Building a Matching Engine is fairly simple. In most cases, all you will need to do is define the matching engine itself and enter some code used to identify matches. The following is an example taken from the API documentation for CRM Core Match. It should be self-explanatory, and you can find the code for the CrmCoreMatchEngine object in your Drupal site's modules directory in crm_core/modules/crm_core_match/includes/crm_core_match.inc.

/**
 * Tells CRM Core Match a matching engine is available.
 * 
 * CRM Core Match can support one or many matching engines. Use this hook to tell CRM Core Match
 * your custom matching engine is available for use.
 * 
 * All matching engines should implement the CrmCoreMatchEngine object to ensure compatibility
 * with CRM Core Match. This is typically done by creating a custom object that extends 
 * CrmCoreMatchEngine.
 * 
 * @param array $values
 * 	An associative array, including a key for the contact record passed by reference.
 */
function hook_crm_core_match_engine_register(){
  return new FooMatchingEngine();
}

/**
 * FooMatchingEngine class
 * 
 * Example of how to define a matching engine of your own.
 *
 * Extends CrmCoreMatchEngine to provide rules for identifying duplicate contacts.
 */
class FooMatchingEngine extends CrmCoreMatchEngine {

  /**
   * The constructor function.
   * 
   * This function identifies the matching engine to CRM Core Match.
   * 
	 * Note that each matching engine can define it's own settings page through the settings array. Matching
	 * engines can feel free to pass in multiple configuration links, if appropriate.
   * 
   */
  public function __construct() {
    $this->name = t('Foo Matching Engine');
    $this->machineName = 'foo_matching_engine';
    $description = 'Handles some kind of matching for new contacts.';
    $this->description = t($description);
    $this->settings = array(
      array(
        'name' => 'settings',
        'path' => 'admin/config/crm-core/match/foo',
        'label' => t('Configuration'),
      ),
    );
  }

  /**
   * The execute method handles the actual matching. You can do anything you want here.
   * 
   * Note that the contact record is passed in by reference. If you want to modify the contact
   * record based on the results of a match, go ahead. 
   *
   * @see CrmCoreMatchEngineInterface::execute()
   */
  public function execute(&$contact, &$ids = array()) {
    // check a public web service to see if a contact is who they say he is
    if ($this->status) {
      // as a best practice, it's always a good idea for each matching engine to have it's 
      // own configuration page, and to specify rules for how to process various contact types
      // in specific ways.
      $base_config = foo_load_contact_type_config($contact->type);
      
      // Check if match is enabled for this contact type.
      if ($base_config['status']) {
        
        // check an external system for a membership id
        if ($contact->member_id != ''){
          $request =  'http://some.api.com/MemberSearch/V1/memberSearch?appid=memberSearch&query=' . $contact->member_id . '&results=4&output=json';
          $response = json_decode($request);
          
          // update the contact record 
          if($contact->extId !== 0){
            $contact->fName = $response['first_name'];
            $contact->lName = $response['last_name'];
            $contact->external_id = $response['member_uid'];
            
            // check CRM Core for a match. If the external ID is already there, return the ID
            // otherwise, this is a new contact, we don't need to do anything
            $query = new EntityFieldQuery();
            $query->entityCondition('entity_type', 'crm_core_contact')
              ->entityCondition('bundle', $contact->type)
              ->fieldCondition('external_id', 'value', $contact->external_id, '=');

            $results = $query->execute();
            
            if($results['crm_core_contact']){
              $ids[] = $results['crm_core_contact'];
            }
          }
        }
      }
    }
  }
}