Developing with CiviCRM Entity and the Drupal API

Published
2017-07-17 08:23
Written by
jackrabbithanna - member of the CiviCRM community - view blog guidelines

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 transistion by enabling them to use some of the standard Drupal API features they have grown accustommed 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 ramications 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 necessay 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 woks 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 familar 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 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 lets make sure our city in our address has every word capitolized, 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. Lets say we want to encapsulate this logic of automatically making the city of an address have uppercase words.  We may want to encapuslate 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

To read the rest of the article, and to get the code to build the Rules action, visit the Skvare.com blog.

Filed under

Comments

Thanks for a very informative article. Looking forward to further articles on the topic.

Anonymous (not verified)
2019-07-01 - 13:08
Anonymous (not verified)
2019-07-01 - 13:10