Is there no api or 'best practice' method for loading all the referenced entities of an entityreference field (aside from manually looping through the array)? I've spent quite a lot of time googling this and searching the entity and entityreference code and keep coming up empty.

I have created two custom entities related by a single entityreference field (it's a shame this can't easily be done with a property rather than a field) and I'm trying to create a loader function that loads the parent entity as well as all the children referenced in the field and can't find any api functions to simplify this. Which I find odd considering it must be a very common need when using entityreference fields.

Or am I missing something so incredibly obvious, it's not googleable, lol.

Comments

nevets’s picture

What is the context of what you are trying to do? For example, if you visit the page for a node (node/{nid}, the node and referenced entities can be displayed (You may need the display suite module to display the actual referenced entity).

WorldFallz’s picture

Thanks for the reply nevets.

These are completely custom non-content (strictly speaking) entities that will eventually be displayed on an admin page. But for right now, I need to pass the fully loaded entity, along with all it's fully loaded children, as an array or object to another function. panels/display suite/views/etc don't apply-- this is all programmatically for now.

I can figure out the raw sql required, but it strikes me there must be an 'entity' or 'entityreference' method for this...

nevets’s picture

As for as I know you can use entity_load() to load the parent, but the entity reference fields by default simply load the target id and not the entity so you would need to loop through the fields. You could either load the referenced entities one by one, or gather them up and can entity_load() with the array of entity ids to load.

jaypan’s picture

I've never found anything for this myself. I just end up manually loading the entities. It would be nice if there were some API for it.

Contact me to contract me for D7 -> D10/11 migrations.

WorldFallz’s picture

Ok, so at least I know I'm not crazy.

I did find this: https://www.drupal.org/node/1021556

Which led me to the following:

function custom_entity_load($id) {
  $my_entity = reset(entity_load('my_custom_entiy_with_er_field', array($id)));
  $wrapper = entity_metadata_wrapper('my_custom_entiy_with_er_field', $my_entity);
  $my_entity->children = $wrapper->my_entity_reference_field->value();

  return $my_entity;
}

The original ER field data is still there at $my_entity->field_my_entity_reference_field but there's now also an array of the entities specified in the field with their complete properties at $my_entity->children.

The child entity isn't fieldable so that works for me. Not sure if it would include all the fields as well for entities that do have them.

kandrupaler’s picture

Thanks for this! I took a cue from your code to hack (yes, sorry, hack) the services module's _node_resource_retrieve function (found in /sites/all/modules/services/resources/node_resource.inc) to render paragraphs. The changed code is as follows - just in case someone wants to use this:

function _node_resource_retrieve($nid) {
  $node = node_load($nid);

  if ($node) {
    $uri = entity_uri('node', $node);
    $node->path = url($uri['path'], array('absolute' => TRUE));
    // Unset uri as it has complete entity and this
    // cause never ending recursion in rendering.
    unset($node->uri);
  }
  //Lets check field_permissions
  $node = services_field_permissions_clean('view', 'node', $node);

  // Start edits by kandrupaler: Load paragraphs as children of $node
  if ($node->field_paragraphs) {
         $wrapper = entity_metadata_wrapper('node', $node);
         $node->children = $wrapper->field_paragraphs->value();
  }
  // End edits by kandrupaler
  return $node;
}

Now you'll find a <children> field in your XML - with the paragraphs data showing in it like it should be. Just grab it and use it in your app.

Of course, field_paragraphs is the name of my paragraphs field. Change it to yours to make it work.

jaypan’s picture

You shouldn't hack modules. In this case, you can implement hook_services_resource_alter(), and change the callback to a custom callback. Use your code in the custom callback, rather than altering the services module.

Contact me to contract me for D7 -> D10/11 migrations.

kandrupaler’s picture

Yes sir. :-)

kandrupaler’s picture

Hey, seriously, I couldn't figure out how to implement this. I found that the hook is hook_services_resources_alter (resources, not resource), but I'm not an expert at this hook stuff. Still beats me. Any quick pointers?

jaypan’s picture

Dump the $resources variable in that hook like this:

die('&lt;pre>' . print_r($resources, TRUE) . '&lt;/pre>');

This will show you the definition of all resources on your system that have been added by any modules, including the services module.

Search for the function name you want to replace.

Then replace it.

For example:

function hook_services_resources_alter(&$resources)
{
  $resources['some_resource']['callback'] = 'my_resource_callback';
}

Then you can define my_resource_callback() and do whatever it is you need to.

Contact me to contract me for D7 -> D10/11 migrations.

kandrupaler’s picture

Thanks a lot. I will do this.

djg_tram’s picture

The correct way to do this today would be to add a function to your custom entity, like this:

public function getFoobars() {
return $this->get('foobars')->referencedEntities();
}

and simply call it from the entity. This can be iterated with a simple foreach. This is just fine for regular use. If you need heavy processing or you don't want to load your entities just to follow the references, you can always use an entityQuery.

guenoz’s picture

a quite dynamic solution (a bit dirty too but I needed it quickly) so you do not need to hard code the name fo the referencing field and it is automatically handled with new referencing field you will add in the future :

in your custom module :

/**
 * Implement hook_field_create_instance().
 */
function MY_CUSTOM_MODULE_field_create_instance() {
  _MY_CUSTOM_MODULE_set_variable_node_back_references();
}

/**
 * Implement hook_field_delete_field().
 */
function MY_CUSTOM_MODULE_field_delete_field() {
  _MY_CUSTOM_MODULE_set_variable_node_back_references();
}

/**
 * Set Variable node_back_references.
 */
function _MY_CUSTOM_MODULE_set_variable_node_back_references() {
  $field_list = db_select('field_config', 'fc')
    ->fields('fc', array('field_name', 'data'))
    ->condition('fc.data', '%"foreign keys";a:1:{s:4:"node"%', 'like')
    ->condition('fc.deleted', 0);
  $field_list->innerJoin('field_config_instance', 'fci', 'fci.field_name = fc.field_name');
  $field_list->rightJoin('node_type', 'n', 'n.type = fci.bundle');
  $fields = $field_list->execute()->fetchAll();

  $fields_array = array();
  foreach ($fields as $field) {
    $unserialized = unserialize($field->data);
    if (isset($unserialized['settings']['handler_settings']['target_bundles'])) {
      foreach ($unserialized['settings']['handler_settings']['target_bundles'] as $bundle) {
        $fields_array[$bundle][] = $field->field_name;
      }
    }
  }

  variable_set('node_back_references', $fields_array);
}

function _MY_CUSTOM_MODULE_get_referencing_nodes($node) {
    $nids = array();
    $fields = variable_get('node_back_references', array());
    if (isset($fields[$node->type])) {
      foreach ($fields[$node->type] as $field) {
        $query = new \EntityFieldQuery();
        $query->entityCondition('entity_type', 'node');
        $query->propertyCondition('status', 1);
        $query->fieldCondition($field, 'target_id', $node->nid);
        $result = $query->execute();
        $nids = isset($result['node']) ? array_merge(array_keys($result['node']), $nids) : $nids;
      }
      $nodes = (!empty($nids)) ? node_load_multiple($nids) : array();

      return $nodes;
    }

    return $nids;
}

where you need to get the parent nodes given the child node :

$nodes = _MY_CUSTOM_MODULE_get_referencing_nodes($node);