You are here

class EntityReferenceHierarchy in Entity Reference Hierarchy 3.x

Same name and namespace in other branches
  1. 8.2 src/Plugin/Field/FieldType/EntityReferenceHierarchy.php \Drupal\entity_hierarchy\Plugin\Field\FieldType\EntityReferenceHierarchy

Plugin implementation of the 'entity_reference_hierarchy' field type.

Plugin annotation


@FieldType(
  id = "entity_reference_hierarchy",
  label = @Translation("Entity reference hierarchy"),
  description = @Translation("Entity parent reference with weight."),
  category = @Translation("Reference"),
  default_widget = "entity_reference_hierarchy_autocomplete",
  default_formatter = "entity_reference_hierarchy_label",
  cardinality = 1,
  list_class = "\Drupal\entity_hierarchy\Plugin\Field\FieldType\EntityReferenceHierarchyFieldItemList"
)

Hierarchy

Expanded class hierarchy of EntityReferenceHierarchy

File

src/Plugin/Field/FieldType/EntityReferenceHierarchy.php, line 30

Namespace

Drupal\entity_hierarchy\Plugin\Field\FieldType
View source
class EntityReferenceHierarchy extends EntityReferenceItem {
  use TreeLockTrait;

  /**
   * Defines the minimum weight of a child (but has the highest priority).
   */
  const HIERARCHY_MIN_CHILD_WEIGHT = -50;

  /**
   * Defines the maximum weight of a child (but has the lowest priority).
   */
  const HIERARCHY_MAX_CHILD_WEIGHT = 50;

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);
    $weight_definition = DataDefinition::create('integer')
      ->setLabel($field_definition
      ->getSetting('weight_label'));
    $weight_definition
      ->addConstraint('Range', [
      'min' => self::HIERARCHY_MIN_CHILD_WEIGHT,
    ]);
    $weight_definition
      ->addConstraint('Range', [
      'max' => self::HIERARCHY_MAX_CHILD_WEIGHT,
    ]);
    $properties['weight'] = $weight_definition;
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = parent::schema($field_definition);
    $schema['columns']['weight'] = [
      'type' => 'int',
      'unsigned' => FALSE,
    ];

    // Add weight index.
    $schema['indexes']['weight'] = [
      'weight',
    ];
    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings() {
    return [
      'weight_label' => t('Weight'),
      'weight_min' => self::HIERARCHY_MIN_CHILD_WEIGHT,
      'weight_max' => self::HIERARCHY_MAX_CHILD_WEIGHT,
    ] + parent::defaultFieldSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
    $elements = parent::fieldSettingsForm($form, $form_state);
    $elements['weight_min'] = [
      '#type' => 'number',
      '#title' => t('Minimum'),
      '#default_value' => $this
        ->getSetting('weight_min'),
    ];
    $elements['weight_max'] = [
      '#type' => 'number',
      '#title' => t('Maximum'),
      '#default_value' => $this
        ->getSetting('weight_max'),
    ];
    $elements['weight_label'] = [
      '#type' => 'textfield',
      '#title' => t('Weight Label'),
      '#default_value' => $this
        ->getSetting('weight_label'),
      '#description' => t('The weight of this child with respect to other children.'),
    ];
    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public static function getPreconfiguredOptions() {

    // In the base EntityReference class, this is used to populate the
    // list of field-types with options for each destination entity type.
    // Too much work, we'll just make people fill that out later.
    // Also, keeps the field type dropdown from getting too cluttered.
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function postSave($update) {
    if (\Drupal::state()
      ->get('entity_hierarchy_disable_writes', FALSE)) {
      return;
    }

    // Get the key factory and tree storage services.
    $nodeKeyFactory = $this
      ->getNodeKeyFactory();
    $storage = $this
      ->getTreeStorage();

    // Get the field name.
    $fieldDefinition = $this
      ->getFieldDefinition();
    $fieldName = $fieldDefinition
      ->getName();
    $entityTypeId = $fieldDefinition
      ->getTargetEntityTypeId();
    $this
      ->lockTree($fieldName, $entityTypeId);

    // Get the parent/child entities and their node-keys in the nested set.
    $parentEntity = $this
      ->get('entity')
      ->getValue();
    if (!$parentEntity) {

      // Parent entity has been deleted.
      // If this node was in the tree, it needs to be moved to a root node.
      $stubNode = $nodeKeyFactory
        ->fromEntity($this
        ->getEntity());
      if (($existingNode = $storage
        ->getNode($stubNode)) && $existingNode
        ->getDepth() > 0) {
        $storage
          ->moveSubTreeToRoot($existingNode);
      }
      $this
        ->releaseLock($fieldName, $entityTypeId);
      return;
    }
    $parentKey = $nodeKeyFactory
      ->fromEntity($parentEntity);
    $childEntity = $this
      ->getEntity();
    $childKey = $nodeKeyFactory
      ->fromEntity($childEntity);

    // Determine if this is a new node in the tree.
    $isNewNode = FALSE;
    if (!($childNode = $storage
      ->getNode($childKey))) {
      $isNewNode = TRUE;

      // As we're going to be adding instead of moving, a key is all we require.
      $childNode = $childKey;
    }

    // Does the parent already exist in the tree.
    if ($existingParent = $storage
      ->getNode($parentKey)) {

      // If there are no siblings, we simply insert/move below.
      $insertPosition = new InsertPosition($existingParent, $isNewNode, InsertPosition::DIRECTION_BELOW);

      // But if there are siblings, we need to ascertain the correct position in
      // the order.
      if ($siblingEntities = $this
        ->getSiblingEntityWeights($storage, $existingParent, $childNode)) {

        // Group the siblings by their weight.
        $weightOrderedSiblings = $this
          ->groupSiblingsByWeight($siblingEntities, $fieldName);
        $weight = $this
          ->get('weight')
          ->getValue();
        $insertPosition = $this
          ->getInsertPosition($weightOrderedSiblings, $weight, $isNewNode) ?: $insertPosition;
      }
      $insertPosition
        ->performInsert($storage, $childNode);
      $this
        ->releaseLock($fieldName, $entityTypeId);
      return;
    }

    // We need to create a node for the parent in the tree.
    $parentNode = $storage
      ->addRootNode($parentKey);
    (new InsertPosition($parentNode, $isNewNode, InsertPosition::DIRECTION_BELOW))
      ->performInsert($storage, $childNode);
    $this
      ->releaseLock($fieldName, $entityTypeId);
  }

  /**
   * Returns the storage handler for the given entity-type.
   *
   * @return \Drupal\Core\Entity\EntityStorageInterface
   *   Storage handler.
   */
  protected function entityTypeStorage($entity_type_id) {
    return \Drupal::entityTypeManager()
      ->getStorage($entity_type_id);
  }

  /**
   * Returns the tree storage.
   *
   * @return \Drupal\entity_hierarchy\Storage\NestedSetStorage
   *   Tree storage.
   */
  protected function getTreeStorage() {
    $fieldDefinition = $this
      ->getFieldDefinition();
    return \Drupal::service('entity_hierarchy.nested_set_storage_factory')
      ->get($fieldDefinition
      ->getName(), $fieldDefinition
      ->getTargetEntityTypeId());
  }

  /**
   * Returns the node factory.
   *
   * @return \Drupal\entity_hierarchy\Storage\NestedSetNodeKeyFactory
   *   The factory.
   */
  protected function getNodeKeyFactory() {
    return \Drupal::service('entity_hierarchy.nested_set_node_factory');
  }

  /**
   * Gets the entity type definition.
   *
   * @return \Drupal\Core\Entity\EntityTypeInterface
   *   Entity type.
   */
  protected function entityTypeDefinition() {
    return \Drupal::entityTypeManager()
      ->getDefinition($this
      ->getFieldDefinition()
      ->getTargetEntityTypeId());
  }

  /**
   * Loads other children of the given parent.
   *
   * @param \PNX\NestedSet\Node[] $siblings
   *   Target siblings.
   *
   * @return \SplObjectStorage
   *   Map of weights keyed by node.
   */
  protected function loadSiblingEntityWeights(array $siblings) {
    $fieldDefinition = $this
      ->getFieldDefinition();
    $entityType = $this
      ->entityTypeDefinition();
    $entityTypeId = $fieldDefinition
      ->getTargetEntityTypeId();
    $entityStorage = $this
      ->entityTypeStorage($entityTypeId);
    $siblingEntities = new \SplObjectStorage();
    $key = $entityType
      ->hasKey('revision') ? $entityType
      ->getKey('revision') : $entityType
      ->getKey('id');
    $parentField = $fieldDefinition
      ->getName();
    $query = $entityStorage
      ->getAggregateQuery();
    $ids = array_map(function (Node $item) {
      return $item
        ->getRevisionId();
    }, $siblings);
    $entities = $query
      ->groupBy($key)
      ->sort($key, 'ASC')
      ->groupBy($parentField . '.weight')
      ->condition($key, $ids, 'IN')
      ->execute();
    $weightSeparator = $fieldDefinition instanceof BaseFieldDefinition ? '__' : '_';
    $entities = array_combine(array_column($entities, $key), array_column($entities, $parentField . $weightSeparator . 'weight'));
    foreach ($siblings as $node) {
      if (!isset($entities[$node
        ->getRevisionId()])) {
        continue;
      }
      $siblingEntities[$node] = (int) $entities[$node
        ->getRevisionId()];
    }
    return $siblingEntities;
  }

  /**
   * Gets siblings.
   *
   * @param \Drupal\entity_hierarchy\Storage\NestedSetStorage $storage
   *   Storage.
   * @param \PNX\NestedSet\Node $parentNode
   *   Existing parent node.
   * @param \PNX\NestedSet\Node|\PNX\NestedSet\NodeKey $childNode
   *   Child node.
   *
   * @return \SplObjectStorage|bool
   *   Map of weights keyed by node or FALSE if no siblings.
   */
  protected function getSiblingEntityWeights(NestedSetStorage $storage, Node $parentNode, $childNode) {
    if ($siblingNodes = array_filter($storage
      ->findChildren($parentNode
      ->getNodeKey()), function (Node $node) use ($childNode) {
      if ($childNode instanceof NodeKey) {

        // Exclude self and all revisions.
        return $childNode
          ->getId() !== $node
          ->getNodeKey()
          ->getId();
      }

      // Exclude self and all revisions.
      return $childNode
        ->getNodeKey()
        ->getId() !== $node
        ->getNodeKey()
        ->getId();
    })) {
      return $this
        ->loadSiblingEntityWeights($siblingNodes);
    }
    return FALSE;
  }

  /**
   * Group siblings by weight.
   *
   * @param \SplObjectStorage $siblingEntities
   *   Sibling entities keyed by nested set nodes.
   * @param string $fieldName
   *   Field name to detect weight from.
   *
   * @return array
   *   Array of nested set nodes grouped by weight.
   */
  public function groupSiblingsByWeight(\SplObjectStorage $siblingEntities, $fieldName) {
    $weightMap = [];
    foreach ($siblingEntities as $node) {
      if (!$siblingEntities
        ->offsetExists($node)) {
        continue;
      }
      $weightMap[$siblingEntities
        ->offsetGet($node)][] = $node;
    }
    ksort($weightMap);
    return $weightMap;
  }

  /**
   * Gets the insert position for the new child.
   *
   * @param array $weightOrderedSiblings
   *   Sibling nodes, grouped by weight.
   * @param int $weight
   *   Desired weight amongst siblings of the new child.
   * @param bool $isNewNode
   *   TRUE if the node is brand new, FALSE if it needs to be moved from
   *   elsewhere in the tree.
   *
   * @return \Drupal\entity_hierarchy\Storage\InsertPosition|bool
   *   Insert position, FALSE if the siblings no longer exist.
   */
  public function getInsertPosition(array $weightOrderedSiblings, $weight, $isNewNode) {
    if (isset($weightOrderedSiblings[$weight])) {

      // There are already nodes at the same weight, insert it with them.
      return new InsertPosition(end($weightOrderedSiblings[$weight]), $isNewNode, InsertPosition::DIRECTION_AFTER);
    }

    // There are no nodes at this weight, we need to find the right position.
    $firstGroup = reset($weightOrderedSiblings);
    $start = key($weightOrderedSiblings);
    if ($weight < $start) {

      // We're going to position before all existing nodes.
      return new InsertPosition(reset($firstGroup), $isNewNode);
    }
    foreach (array_keys($weightOrderedSiblings) as $weightPosition) {
      if ($weight < $weightPosition) {
        return new InsertPosition(reset($weightOrderedSiblings[$weightPosition]), $isNewNode);
      }
    }

    // We're inserting at the end.
    $lastGroup = end($weightOrderedSiblings);
    if (!$lastGroup) {
      return FALSE;
    }
    return new InsertPosition(end($lastGroup), $isNewNode, InsertPosition::DIRECTION_AFTER);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
EntityReferenceHierarchy::defaultFieldSettings public static function Defines the field-level settings for this plugin. Overrides EntityReferenceItem::defaultFieldSettings
EntityReferenceHierarchy::entityTypeDefinition protected function Gets the entity type definition.
EntityReferenceHierarchy::entityTypeStorage protected function Returns the storage handler for the given entity-type.
EntityReferenceHierarchy::fieldSettingsForm public function Returns a form for the field-level settings. Overrides EntityReferenceItem::fieldSettingsForm
EntityReferenceHierarchy::getInsertPosition public function Gets the insert position for the new child.
EntityReferenceHierarchy::getNodeKeyFactory protected function Returns the node factory.
EntityReferenceHierarchy::getPreconfiguredOptions public static function Returns preconfigured field options for a field type. Overrides EntityReferenceItem::getPreconfiguredOptions
EntityReferenceHierarchy::getSiblingEntityWeights protected function Gets siblings.
EntityReferenceHierarchy::getTreeStorage protected function Returns the tree storage.
EntityReferenceHierarchy::groupSiblingsByWeight public function Group siblings by weight.
EntityReferenceHierarchy::HIERARCHY_MAX_CHILD_WEIGHT constant Defines the maximum weight of a child (but has the lowest priority).
EntityReferenceHierarchy::HIERARCHY_MIN_CHILD_WEIGHT constant Defines the minimum weight of a child (but has the highest priority).
EntityReferenceHierarchy::loadSiblingEntityWeights protected function Loads other children of the given parent.
EntityReferenceHierarchy::postSave public function Defines custom post-save behavior for field values. Overrides FieldItemBase::postSave
EntityReferenceHierarchy::propertyDefinitions public static function Defines field item properties. Overrides EntityReferenceItem::propertyDefinitions
EntityReferenceHierarchy::schema public static function Returns the schema for the field. Overrides EntityReferenceItem::schema
EntityReferenceItem::calculateDependencies public static function Calculates dependencies for field items. Overrides FieldItemBase::calculateDependencies
EntityReferenceItem::calculateStorageDependencies public static function Calculates dependencies for field items on the storage level. Overrides FieldItemBase::calculateStorageDependencies
EntityReferenceItem::defaultStorageSettings public static function Defines the storage-level settings for this plugin. Overrides FieldItemBase::defaultStorageSettings 1
EntityReferenceItem::fieldSettingsAjaxProcess public static function Render API callback: Processes the field settings form.
EntityReferenceItem::fieldSettingsAjaxProcessElement public static function Adds entity_reference specific properties to AJAX form elements from the field settings form.
EntityReferenceItem::fieldSettingsFormValidate public static function Form element validation handler; Invokes selection plugin's validation.
EntityReferenceItem::formProcessMergeParent public static function Render API callback: Moves entity_reference specific Form API elements (i.e. 'handler_settings') up a level for easier processing by the validation and submission handlers.
EntityReferenceItem::generateSampleValue public static function Generates placeholder field values. Overrides FieldItemBase::generateSampleValue 1
EntityReferenceItem::getConstraints public function Gets a list of validation constraints. Overrides TypedData::getConstraints
EntityReferenceItem::getPossibleOptions public function Returns an array of possible values with labels for display. Overrides OptionsProviderInterface::getPossibleOptions
EntityReferenceItem::getPossibleValues public function Returns an array of possible values. Overrides OptionsProviderInterface::getPossibleValues
EntityReferenceItem::getRandomBundle protected static function Gets a bundle for a given entity type and selection options.
EntityReferenceItem::getSettableOptions public function Returns an array of settable values with labels for display. Overrides OptionsProviderInterface::getSettableOptions
EntityReferenceItem::getSettableValues public function Returns an array of settable values. Overrides OptionsProviderInterface::getSettableValues
EntityReferenceItem::getValue public function Gets the data value. Overrides Map::getValue
EntityReferenceItem::hasNewEntity public function Determines whether the item holds an unsaved entity.
EntityReferenceItem::isEmpty public function Determines whether the data structure is empty. Overrides Map::isEmpty
EntityReferenceItem::mainPropertyName public static function Returns the name of the main property, if any. Overrides FieldItemBase::mainPropertyName
EntityReferenceItem::onChange public function React to changes to a child property or item. Overrides Map::onChange
EntityReferenceItem::onDependencyRemoval public static function Informs the plugin that a dependency of the field will be deleted. Overrides FieldItemBase::onDependencyRemoval
EntityReferenceItem::preSave public function Defines custom presave behavior for field values. Overrides FieldItemBase::preSave 1
EntityReferenceItem::settingsAjax public static function Ajax callback for the handler settings form.
EntityReferenceItem::settingsAjaxSubmit public static function Submit handler for the non-JS case.
EntityReferenceItem::setValue public function Sets the data value. Overrides FieldItemBase::setValue
EntityReferenceItem::storageSettingsForm public function Returns a form for the storage-level settings. Overrides FieldItemBase::storageSettingsForm 1
FieldItemBase::delete public function Defines custom delete behavior for field values. Overrides FieldItemInterface::delete 2
FieldItemBase::deleteRevision public function Defines custom revision delete behavior for field values. Overrides FieldItemInterface::deleteRevision
FieldItemBase::fieldSettingsFromConfigData public static function Returns a settings array in the field type's canonical representation. Overrides FieldItemInterface::fieldSettingsFromConfigData 1
FieldItemBase::fieldSettingsToConfigData public static function Returns a settings array that can be stored as a configuration value. Overrides FieldItemInterface::fieldSettingsToConfigData 1
FieldItemBase::getEntity public function Gets the entity that field belongs to. Overrides FieldItemInterface::getEntity
FieldItemBase::getFieldDefinition public function Gets the field definition. Overrides FieldItemInterface::getFieldDefinition
FieldItemBase::getLangcode public function Gets the langcode of the field values held in the object. Overrides FieldItemInterface::getLangcode
FieldItemBase::getSetting protected function Returns the value of a field setting.
FieldItemBase::getSettings protected function Returns the array of field settings.
FieldItemBase::storageSettingsFromConfigData public static function Returns a settings array in the field type's canonical representation. Overrides FieldItemInterface::storageSettingsFromConfigData 2
FieldItemBase::storageSettingsToConfigData public static function Returns a settings array that can be stored as a configuration value. Overrides FieldItemInterface::storageSettingsToConfigData 2
FieldItemBase::view public function Returns a renderable array for a single field item. Overrides FieldItemInterface::view
FieldItemBase::writePropertyValue protected function Different to the parent Map class, we avoid creating property objects as far as possible in order to optimize performance. Thus we just update $this->values if no property object has been created yet. Overrides Map::writePropertyValue
FieldItemBase::__construct public function Constructs a TypedData object given its definition and context. Overrides TypedData::__construct 1
FieldItemBase::__get public function Magic method: Gets a property value. Overrides FieldItemInterface::__get 2
FieldItemBase::__isset public function Magic method: Determines whether a property is set. Overrides FieldItemInterface::__isset
FieldItemBase::__set public function Magic method: Sets a property value. Overrides FieldItemInterface::__set 1
FieldItemBase::__unset public function Magic method: Unsets a property. Overrides FieldItemInterface::__unset
Map::$definition protected property The data definition. Overrides TypedData::$definition
Map::$properties protected property The array of properties.
Map::$values protected property An array of values for the contained properties.
Map::applyDefaultValue public function Applies the default value. Overrides TypedData::applyDefaultValue 4
Map::get public function Gets a property object. Overrides ComplexDataInterface::get
Map::getIterator public function
Map::getProperties public function Gets an array of property objects. Overrides ComplexDataInterface::getProperties
Map::getString public function Returns a string representation of the data. Overrides TypedData::getString
Map::set public function Sets a property value. Overrides ComplexDataInterface::set
Map::toArray public function Returns an array of all property values. Overrides ComplexDataInterface::toArray 1
Map::__clone public function Magic method: Implements a deep clone.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TreeLockTrait::$lockBackend protected property Lock backend.
TreeLockTrait::getLockName protected function Gets lock name.
TreeLockTrait::lockBackend protected function Gets lock backend.
TreeLockTrait::lockTree protected function Locks tree.
TreeLockTrait::releaseLock protected function Releases lock.
TreeLockTrait::setLockBackend public function Sets lock backend.
TypedData::$name protected property The property name.
TypedData::$parent protected property The parent typed data object.
TypedData::createInstance public static function Constructs a TypedData object given its definition and context. Overrides TypedDataInterface::createInstance
TypedData::getDataDefinition public function Gets the data definition. Overrides TypedDataInterface::getDataDefinition
TypedData::getName public function Returns the name of a property or item. Overrides TypedDataInterface::getName
TypedData::getParent public function Returns the parent data structure; i.e. either complex data or a list. Overrides TypedDataInterface::getParent
TypedData::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition
TypedData::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
TypedData::getPropertyPath public function Returns the property path of the data. Overrides TypedDataInterface::getPropertyPath
TypedData::getRoot public function Returns the root of the typed data tree. Overrides TypedDataInterface::getRoot
TypedData::setContext public function Sets the context of a property or item via a context aware parent. Overrides TypedDataInterface::setContext
TypedData::validate public function Validates the currently set data value. Overrides TypedDataInterface::validate
TypedDataTrait::$typedDataManager protected property The typed data manager used for creating the data types.
TypedDataTrait::getTypedDataManager public function Gets the typed data manager. 2
TypedDataTrait::setTypedDataManager public function Sets the typed data manager. 2