Developing with CiviCRM Entity and the Drupal API

If you are a Drupal developer coming new to CiviCRM, it can be a bit of a "culture shock" to realize that CiviCRM is not your typical Drupal module.

CiviCRM has a separate and independent evolution and ecosystem, and its standard practices and APIs reflect that. From installation of the module itself to creating customizations and modifications of its standard behavior, you are entering into a different "world" when you implement and develop client solutions with CiviCRM.

CiviCRM Entity can help a Drupal developer make the transition by enabling them to use some of the standard Drupal API features they have grown accustomed to, while still providing insight into the data structures and interconnections of CiviCRM.

For people who spend the majority of their time developing in CiviCRM, it can feel the same way, in reverse. For all-day CiviCRM developers, CiviCRM Entity can be an opportunity to better leverage your Drupal CMS for customizations and new features.  So this introduction to Developing with CiviCRM Entity and the Drupal API is for you too.

How's it work?

The premise behind CiviCRM Entity is really quite simple, though its ruminations are profound. What CiviCRM Entity does is automate the process of exposing CiviCRM API entities and actions as Drupal entity types. Basically, a Drupal entity type is the standard Drupal data model for a database table.  You map metadata to columns, and this provides one consistent way to store, retrieve, and manipulate database table data for Drupal Core and the entire ecosystem of contributed modules that can go with it. Because CiviCRM does not by itself engage its data with Drupal's Entity API, the majority of Drupal modules are not aware of CiviCRM's data and cannot act on it.

CiviCRM Entity implements all the necessary hooks to define the CiviCRM data as Drupal entity types.  It registers the entity types with hook_entity_info(), sets up the metadata with a hook_entity_property_info_alter() implementation, and extends the default Entity API objects and controllers.  Inside the controller responsible for load, save, and delete, instead of using Drupal's standard PDO SQL query operations, CiviCRM API calls are used.  This makes CiviCRM Entity a "remote entity" module, but specifically designed to work with CiviCRM only. 

This bit alone does the most important thing. It makes Drupal think CiviCRM data is Drupal data.  You can attach Drupal fields, to CiviCRM data.  You can use Drupal's entity_metadata_wrapper. All the rest of the code in the module and its submodules dealing with specific integration enhancements is just gravy.

Using the metadata wrapper, we built up one bit of Drupal Form API code, that works with all the entities, and provides Drupal standard CRUD forms. Now you got Manage Fields and Manage Display pages for each entity type. Now the Rules module will play nice. Now Drupal developers can build cool custom stuff, using their familiar tools.

Entity Field Query

EntityFieldQuery is Drupal 7's standard programmatic way to query the database tables exposed as entities. So let's say we want to find all the Home location type addresses for a particular contact. If there are results there will be an array with the key of the entity type name containing objects keyed by id.

$contact_id = 3099;
$query = new EntityFieldQuery();
$address_ids = $query->entityCondition('entity_type', 'civicrm_address')
  ->propertyCondition('contact_id', $contact_id)
  ->propertyCondition('location_type_id', 1)
  ->execute();
if (!empty($address_ids['civicrm_address'])) {
// do something
}

Load Drupal Entity objects

Following the example above, we have a query result, and now want to load the entity objects.

if (!empty($address_ids['civicrm_address'])) {
  // entity_load returns an array of entity objects keyed by id
  $address_entities = entity_load('civicrm_address', array_keys($address_ids['civicrm_address']));
 
  // maybe you just want the individual entity objects...
  foreach ($address_ids['civicrm_address'] as $id => $result) {
    $address_entity = entity_load_single('civicrm_address', $id);
    // get the city of the address
    $city = $address_entity->city;
  }
}

Saving entities

Now let's make sure our city in our address has every word capitalized, and save the address. The data as you see it in the CiviCRM admin backend will immediately reflect the changes.

$address_entity->city = ucwords($address_entity->city);
entity_save('civicrm_address', $address_entity);

Deleting Entities

If you want to delete an entity, you can use entity_delete().  Remember that these functions eventually get to the controller, which is a wrapper around CiviCRM API calls.  This matters especially for contacts because by default deleting contacts sends them to the CiviCRM "trash", instead of completely deleting them.

entity_delete('civicrm_address', $address_entity->id);

The Entity Metadata Wrapper 

If you start getting serious about programmatically manipulating entities, you want to start using the Entity Metadata Wrapper. This object encapsulates all these operations in an object oriented way.  It becomes especially useful when you are manipulating multi-lingual fields. It also can use entity level validation based on the entity metadata for each property of the entity type.  I would encourage its use in favor of manipulating the entity object directly or using the entity_X functions. The code is much more readable and easier to write, and with validation, it is much safer. There is a great article about the benefits of the wrapper which goes into detail.

You can pass the entity_metadata_wrapper function the entity object, or simply the id of the entity, and it will lazy load the object. If all you have is the id to start, no need to load the entity object first.

$address_wrapper = entity_metadata_wrapper('civicrm_address', $address_id);
$city = $address_wrapper->city->value();
if($address_wrapper->city->validate(ucwords($city))) {
  $address_wrapper->city = ucwords($city);
  $address_wrapper->save();
}
// get the updated entity object
$updated_address_entity = $address_wrapper->value();
 
// nevermind, lets just delete the entity
$address_wrapper->delete();

Custom Rules Action Example

A very practical use case of using the Drupal API for CiviCRM is creating custom Rules conditions or actions. Let's say we want to encapsulate this logic of automatically making the city of an address have upper case words.  We may want to encapsulate functionality like this and pass it on to our site builders or clients who can use it when they need it. Once you find out how easy it is to create custom Rules actions, you'll have a powerful tool in your toolbox. There's lots of documentation on the web for doing this. 

Lets put this in a little module, I'm calling it civicrm_custom. Create a directory in your sites/all/modules directory named civicrm_custom

Create a text file in your new directory, named civicrm_custom.info file, which should contain:

name = CiviCRM Customs
description = Provides a custom Rules action
core = 7.x
package = Custom
version = "7.x-1.0"
 
dependencies[] = civicrm_entity
dependencies[] = rules

Create another text file called civicrm_custom.module in the same directory.  I like to see the code first, then get the explanation, so here it is:

/**
 * Implements hook_rules_action_info().
 *
 * @see http://drupalcontrib.org/api/drupal/contributions!rules!rules.api.php/function/hook_rules_action_info/7
 */
function civicrm_custom_rules_action_info() {
 $actions = array();
 $actions['civicrm_custom_rules_action_make_address_city_uppercase'] = array(
   'label' => t('Make city words uppercase'),
   'group' => t('CiviCRM Address'),
   'parameter' => array(
     'address' => array (
       'type' => 'civicrm_address',
       'label' => t('CiviCRM Address'),
       'description' => t('CiviCRM Address entity'),
       'wrapped' => TRUE, // pass the metadata wrapper to the callback function
     ),
   ),
 );
 return $actions;
}
 
/**
 * Callback function for the 'Make city words uppercase' Rules action
 */
function civicrm_custom_rules_action_make_address_city_uppercase($address_wrapper) {
  if (!empty($address_wrapper->city->value()) && $address_wrapper->city->validate(ucwords($address_wrapper->city->value()))) {
    $address_wrapper->city = ucwords($address_wrapper->city->value());
    $address_wrapper->save();
  }
}

That's all you need, its simple really. Install the module and we can let our site builders use this new Rules action!

The top level array index of the $actions array will be the name of the callback function that Rules looks for when the action is invoked. The parameters you define there will be passed to the callback function.  You set the label to what is displayed when you choose the option and the Rules action group that it is in.  Notice the 'wrapped' option.  If that is FALSE (or left out), then the entity object, not the metadata wrapper will be passed to the callback function.

Perhaps we have a Rule that we'll call "Standardize address", and this action will be just one action that the site builders want to use. 

To build the Rule

  1. go to [drupal_root]/admin/config/workflow/rules
  2. Click the Add New Rule link at the top of the page
  3. Enter "Sanitize Address" for the Rule name
  4. Select "CiviCRM Address has been updated" for the "React on event" field, and click save
  5. If you also want this Rule to trigger when an Address is created, add a "CiviCRM Address has been created" event trigger by clicking the "Add Event" link
  6. In the Actions group, click the Add Actions link
  7. In the "Select the action to add" field, find the "CiviCRM Address" group, and select the "Make city words upper case" action
  8. In the CiviCRM Address parameter field group, Data Selector field, enter "civicrm-address" 
  9. Save and Save, and you are done!

Now whether you create or update an address with the CiviCRM API or by using the Contact Summary page in the CiviCRM Admin pages, any city entered will have capitalized words.

Here's the import code for the Rule:

{ "rules_sanitize_address" : {
    "LABEL" : "Sanitize Address",
    "PLUGIN" : "reaction rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "civicrm_custom", "civicrm" ],
    "ON" : { "civicrm_address_edit" : [], "civicrm_address_create" : [] },
    "DO" : [
      { "civicrm_custom_rules_action_make_address_city_uppercase" : { "address" : [ "civicrm-address" ] } }
    ]
  }
}

I hope you enjoyed this introduction to developing with CiviCRM and the Drupal API.  Go make some custom Rules actions, and much more.

 

CiviCRM Entity

Tags: