View source  
  <?php
const RELATION_FIELD_NAME = 'endpoints';
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\Element;
use Drupal\field\Entity\FieldConfig;
use Drupal\relation\Entity\Relation;
use Drupal\relation\Entity\RelationType;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\relation\RelationTypeInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Field\FieldItemListInterface;
function relation_theme() {
  $theme = array(
    'relation' => array(
      'render element' => 'elements',
      'template' => 'relation',
    ),
    'relation_admin_content' => array(
      'variables' => array(
        'relations' => NULL,
      ),
    ),
  );
  return $theme;
}
function template_preprocess_relation(&$variables) {
  $variables['relation'] = $variables['elements']['#relation'];
  $variables += array(
    'content' => array(),
  );
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}
function relation_get_relation_types_options() {
  $options = array();
  foreach (RelationType::loadMultiple() as $relation_type) {
    $options[$relation_type
      ->id()] = $relation_type
      ->label();
  }
  return $options;
}
function relation_query_enforce_distinct_endpoints_alter(AlterableInterface $query) {
  $arity = 0;
  
  $conditions = $query
    ->conditions();
  foreach (Element::children($conditions) as $c) {
    $condition = $conditions[$c];
    if ($condition['field'] == 'relation.arity') {
      $arity = $condition['value'];
      break;
    }
  }
  
  for ($i = 0; $i < $arity; $i++) {
    for ($k = $i + 1; $k < $arity; $k++) {
      $left_suffix = !$i ? '' : '_' . ($i + 1);
      $right_suffix = !$k ? '' : '_' . ($k + 1);
      $column_left = 'relation__endpoints' . $left_suffix . '.endpoints_r_index';
      $column_right = 'relation__endpoints' . $right_suffix . '.endpoints_r_index';
      $query
        ->where("{$column_left} != {$column_right}");
    }
  }
}
function relation_clear_related_entities_cache(FieldItemListInterface $endpoints) {
  drupal_static_reset('relation_get_related_entity');
  foreach ($endpoints as $endpoint) {
    \Drupal::cache()
      ->delete('relation:' . $endpoint->entity_type . ':' . $endpoint->entity_id, 'cache', TRUE);
  }
}
function relation_query($entity_type = NULL, $entity_id = NULL, $r_index = NULL) {
  $query = Drupal::entityQuery('relation');
  if ($entity_type) {
    relation_query_add_related($query, $entity_type, $entity_id, $r_index);
  }
  return $query;
}
function relation_query_add_related(QueryInterface $query, $entity_type, $entity_id = NULL, $r_index = NULL) {
  $group = $query
    ->andConditionGroup()
    ->condition('endpoints.entity_type', $entity_type, '=');
  if (isset($entity_id)) {
    $operator = is_array($entity_id) ? 'IN' : '=';
    $group
      ->condition('endpoints.entity_id', $entity_id, $operator);
  }
  if (isset($r_index)) {
    $group
      ->condition('endpoints.r_index', $r_index, '=');
  }
  $query
    ->condition($group);
  return $query;
}
function relation_get_related_entity($entity_type, $entity_id, $relation_type = NULL, $r_index = NULL) {
  
  $items =& drupal_static(__FUNCTION__);
  $request_key = "{$entity_type}:{$entity_id}";
  $cache_key = "{$request_key}:{$relation_type}:{$r_index}";
  if (isset($items[$cache_key])) {
    $entities = $items[$cache_key];
  }
  elseif ($cached = \Drupal::cache()
    ->get("relation:{$cache_key}")) {
    $entities = $cached->data;
    $items[$cache_key] = $entities;
  }
  else {
    $query = Drupal::entityQuery('relation');
    relation_query_add_related($query, $entity_type, $entity_id, $r_index)
      ->range(0, 1);
    if ($relation_type) {
      $query
        ->condition('relation_type', $relation_type);
    }
    $results = $query
      ->execute();
    $relation_id = reset($results);
    if ($relation_id) {
      $relation = Relation::load($relation_id);
      if ($relation->arity->value == 1) {
        $entities = FALSE;
      }
      else {
        $entities = $relation->endpoints;
      }
    }
    else {
      $entities = FALSE;
    }
    \Drupal::cache()
      ->set("relation:{$cache_key}", $entities);
    $items[$cache_key] = $entities;
  }
  if ($entities) {
    $first_entity_key = $entities[0]->entity_type . ':' . $entities[0]->entity_id;
    if (isset($r_index)) {
      $request_key = $request_key . ':' . $r_index;
      $first_entity_key = $first_entity_key . ':' . $entities[0]->r_index;
    }
    if ($request_key == $first_entity_key) {
      $storage_handler = \Drupal::entityTypeManager()
        ->getStorage($entities[1]->entity_type);
      return $storage_handler
        ->load($entities[1]->entity_id);
    }
    $storage_handler = \Drupal::entityTypeManager()
      ->getStorage($entities[0]->entity_type);
    return $storage_handler
      ->load($entities[0]->entity_id);
  }
  return FALSE;
}
function relation_entity_delete(EntityInterface $entity) {
  if ($entity
    ->getEntityTypeId() == 'relation' && $entity->endpoints) {
    relation_clear_related_entities_cache($entity->endpoints);
  }
  
  $relations_exist = \Drupal::entityTypeManager()
    ->getStorage('relation')
    ->getQuery()
    ->count()
    ->execute();
  if ($relations_exist) {
    
    $relation_ids = relation_query($entity
      ->getEntityTypeId(), $entity
      ->id())
      ->execute();
    
    $relations_to_delete = array();
    foreach (Relation::loadMultiple($relation_ids) as $relation) {
      
      foreach ($relation->endpoints as $key => $endpoint) {
        if ($endpoint->entity_id == $entity
          ->id() && $endpoint->entity_type == $entity
          ->getEntityTypeId()) {
          unset($relation->endpoints[$key]);
        }
      }
      
      $relation_type = RelationType::load($relation
        ->bundle());
      $arity = count($relation->endpoints);
      if ($relation_type && $arity < $relation_type->min_arity) {
        
        array_push($relations_to_delete, $relation
          ->id());
      }
      else {
        
        $relation
          ->save();
      }
    }
    if (!empty($relations_to_delete)) {
      $storage_handler = \Drupal::entityTypeManager()
        ->getStorage('relation');
      $relations = $storage_handler
        ->loadMultiple($relations_to_delete);
      $storage_handler
        ->delete($relations);
      \Drupal::logger('relation')
        ->error('Relations @relations have been deleted.', array(
        '@relations' => implode(', ', $relations_to_delete),
      ));
    }
  }
}
function relation_add_endpoint_field(RelationTypeInterface $relation_type) {
  $field = FieldStorageConfig::loadByName('relation', RELATION_FIELD_NAME);
  $instance = FieldConfig::loadByName('relation', $relation_type
    ->id(), RELATION_FIELD_NAME);
  if (empty($field)) {
    $field = FieldStorageConfig::create(array(
      'field_name' => RELATION_FIELD_NAME,
      'entity_type' => 'relation',
      'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
      'type' => 'relation_endpoint',
      'locked' => TRUE,
    ));
    $field
      ->save();
  }
  if ($field && empty($instance)) {
    
    $instance = FieldConfig::create(array(
      'field_storage' => $field,
      'bundle' => $relation_type
        ->id(),
      'label' => t('Endpoints'),
      'settings' => array(),
    ));
    $instance
      ->save();
    
    $entity_form_display = \Drupal::entityTypeManager()
      ->getStorage('entity_form_display')
      ->load('relation.' . $relation_type
      ->id() . '.default');
    if (!$entity_form_display) {
      $entity_form_display = EntityFormDisplay::create(array(
        'targetEntityType' => 'relation',
        'bundle' => $relation_type
          ->id(),
        'mode' => 'default',
        'status' => TRUE,
      ));
    }
    $entity_form_display
      ->setComponent(RELATION_FIELD_NAME, array(
      'type' => 'relation_endpoint',
    ))
      ->save();
    
    $display = \Drupal::entityTypeManager()
      ->getStorage('entity_view_display')
      ->load('relation.' . $relation_type
      ->id() . '.default');
    if (!$display) {
      $display = EntityViewDisplay::create(array(
        'targetEntityType' => 'relation',
        'bundle' => $relation_type
          ->id(),
        'mode' => 'default',
        'status' => TRUE,
      ));
    }
    $display
      ->setComponent(RELATION_FIELD_NAME, array(
      'label' => 'hidden',
      'type' => 'relation_endpoint',
    ))
      ->save();
  }
}