entity_reference_uuid-8.x-1.x-dev/src/Plugin/Field/FieldType/EntityReferenceUuidItem.php
src/Plugin/Field/FieldType/EntityReferenceUuidItem.php
<?php namespace Drupal\entity_reference_uuid\Plugin\Field\FieldType; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\TypedData\EntityDataDefinition; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\DataReferenceDefinition; use Drupal\Core\TypedData\DataReferenceTargetDefinition; /** * Defines the 'entity_reference_uuid' entity field type. * * Supported settings (below the definition's 'settings' key) are: * - target_type: The entity type to reference. Required. * * @FieldType( * id = "entity_reference_uuid", * label = @Translation("Entity reference UUID"), * description = @Translation("An entity field containing an entity reference by UUID."), * category = "reference", * default_widget = "entity_reference_autocomplete", * default_formatter = "entity_reference_label", * list_class = "\Drupal\entity_reference_uuid\EntityReferenceUuidFieldItemList", * ) */ class EntityReferenceUuidItem extends EntityReferenceItem { /** * {@inheritdoc} */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { $settings = $field_definition->getSettings(); $target_type_info = \Drupal::entityTypeManager()->getDefinition($settings['target_type']); $properties = parent::propertyDefinitions($field_definition); $target_uuid_definition = DataReferenceTargetDefinition::create('string') ->setLabel(new TranslatableMarkup('@label UUID', ['@label' => $target_type_info->getLabel()])); $target_uuid_definition->setRequired(TRUE); $properties['target_uuid'] = $target_uuid_definition; $properties['entity'] = DataReferenceDefinition::create('entity') ->setLabel($target_type_info->getLabel()) ->setDescription(new TranslatableMarkup('The referenced entity by UUID')) // The entity object is computed out of the entity ID. ->setComputed(TRUE) ->setReadOnly(FALSE) ->setTargetDefinition(EntityDataDefinition::create($settings['target_type'])) // We can add a constraint for the target entity type. The list of // referenceable bundles is a field setting, so the corresponding // constraint is added dynamically in ::getConstraints(). ->addConstraint('EntityType', $settings['target_type']); return $properties; } /** * {@inheritdoc} */ public static function mainPropertyName() { return 'target_uuid'; } /** * {@inheritdoc} */ public static function schema(FieldStorageDefinitionInterface $field_definition) { $columns = [ 'target_uuid' => [ 'description' => 'The UUID of the target entity.', 'type' => 'varchar_ascii', 'length' => 128, ], ]; $schema = [ 'columns' => $columns, 'indexes' => [ 'target_uuid' => ['target_uuid'], ], ]; return $schema; } /** * {@inheritdoc} */ public function setValue($values, $notify = TRUE) { if (isset($values) && !is_array($values)) { // If either a scalar or an object was passed as the value for the item, // assign it to the 'entity' or 'target_uuid' depending on values type. if (is_object($values)) { $this->set('entity', $values, $notify); } else { $this->set('target_uuid', $values, $notify); } } else { parent::setValue($values, FALSE); // Support setting the field item with only one property, but make sure // values stay in sync if only property is passed. // NULL is a valid value, so we use array_key_exists(). if (is_array($values) && array_key_exists('target_uuid', $values) && !isset($values['entity'])) { $this->onChange('target_uuid', FALSE); } elseif (is_array($values) && !array_key_exists('target_uuid', $values) && isset($values['entity'])) { $this->onChange('entity', FALSE); } elseif (is_array($values) && array_key_exists('target_uuid', $values) && isset($values['entity'])) { // If both properties are passed, verify the passed values match. The // only exception we allow is when we have a new entity: in this case // its actual id and target_uuid will be different, due to the new // entity marker. $entity_uuid = $this->get('entity')->get('uuid'); // If the entity has been saved and we're trying to set both the // target_uuid and the entity values with a non-null target UUID, then // the value for target_uuid should match the UUID of the entity value. if (!$this->entity->isNew() && $values['target_uuid'] !== NULL && ($entity_uuid !== $values['target_uuid'])) { throw new \InvalidArgumentException('The target UUID and entity passed to the entity reference item do not match.'); } } // Notify the parent if necessary. if ($notify && $this->parent) { $this->parent->onChange($this->getName()); } } } /** * {@inheritdoc} */ public function getValue() { $values = parent::getValue(); // If there is an unsaved entity, return it as part of the field item values // to ensure idempotency of getValue() / setValue(). if ($this->hasNewEntity()) { $values['entity'] = $this->entity; } return $values; } /** * {@inheritdoc} */ public function onChange($property_name, $notify = TRUE) { // Make sure that the target UUID and the target property stay in sync. if ($property_name == 'entity') { $property = $this->get('entity'); if ($target_uuid = $property->isTargetNew() ? NULL : $property->getValue()->uuid()) { $this->writePropertyValue('target_uuid', $target_uuid); } } elseif ($property_name == 'target_uuid') { $property = $this->get('entity'); $entity_type = $property->getDataDefinition()->getConstraint('EntityType'); $entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadByProperties(['uuid' => $this->get('target_uuid')->getValue()]); if ($entity = array_shift($entities)) { $this->writePropertyValue('target_id', $entity->id()); $this->writePropertyValue('entity', $entity); } } parent::onChange($property_name, $notify); } /** * {@inheritdoc} */ public function isEmpty() { // Avoid loading the entity by first checking the 'target_uuid'. if ($this->target_uuid !== NULL) { return FALSE; } if ($this->entity && $this->entity instanceof EntityInterface) { return FALSE; } return TRUE; } /** * {@inheritdoc} */ public function preSave() { if ($this->hasNewEntity()) { // Save the entity if it has not already been saved by some other code. if ($this->entity->isNew()) { $this->entity->save(); } // Make sure the parent knows we are updating this property so it can // react properly. $this->target_uuid = $this->entity->uuid(); } if (!$this->isEmpty() && $this->target_uuid === NULL) { $this->target_uuid = $this->entity->uuid(); } } /** * {@inheritdoc} */ public static function generateSampleValue(FieldDefinitionInterface $field_definition) { $manager = \Drupal::service('plugin.manager.entity_reference_selection'); // Instead of calling $manager->getSelectionHandler($field_definition) // replicate the behavior to be able to override the sorting settings. $options = [ 'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'), 'handler' => $field_definition->getSetting('handler'), 'handler_settings' => $field_definition->getSetting('handler_settings') ?: [], 'entity' => NULL, ]; $entity_type = \Drupal::entityTypeManager()->getDefinition($options['target_type']); $options['handler_settings']['sort'] = [ 'field' => $entity_type->getKey('uuid'), 'direction' => 'DESC', ]; $selection_handler = $manager->getInstance($options); // Select a random number of references between the last 50 referenceable // entities created. if ($referenceable = $selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 50)) { $group = array_rand($referenceable); return ['target_uuid' => array_rand($referenceable[$group])]; } return []; } /** * Determines whether the item holds an unsaved entity. * * This is notably used for "autocreate" widgets, and more generally to * support referencing freshly created entities (they will get saved * automatically as the hosting entity gets saved). * * @return bool * TRUE if the item holds an unsaved entity. */ public function hasNewEntity() { return !$this->isEmpty() && $this->target_uuid === NULL && $this->entity->isNew(); } /** * {@inheritdoc} */ public static function getPreconfiguredOptions() { $options = []; // Add all the commonly referenced entity types as distinct pre-configured // options. $entity_types = \Drupal::entityTypeManager()->getDefinitions(); $common_references = array_filter($entity_types, function (EntityTypeInterface $entity_type) { return $entity_type->isCommonReferenceTarget(); }); /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ foreach ($common_references as $entity_type) { $options[$entity_type->id()] = [ 'label' => $entity_type->getLabel() . ' ' . t('by UUID'), 'field_storage_config' => [ 'settings' => [ 'target_type' => $entity_type->id(), ], ], ]; } return $options; } }