EntityReferenceItem.php 10.5 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem.
6 7
 */

8
namespace Drupal\Core\Field\Plugin\Field\FieldType;
9

10
use Drupal\Core\Entity\EntityInterface;
11
use Drupal\Core\Entity\EntityTypeInterface;
12
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
13
use Drupal\Core\Field\FieldDefinitionInterface;
14
use Drupal\Core\Field\FieldItemBase;
15
use Drupal\Core\Field\FieldStorageDefinitionInterface;
16
use Drupal\Core\TypedData\DataDefinition;
17
use Drupal\Core\TypedData\DataReferenceDefinition;
18 19

/**
20
 * Defines the 'entity_reference' entity field type.
21
 *
22 23 24 25 26
 * Supported settings (below the definition's 'settings' key) are:
 * - target_type: The entity type to reference. Required.
 * - target_bundle: (optional): If set, restricts the entity bundles which may
 *   may be referenced. May be set to an single bundle, or to an array of
 *   allowed bundles.
27
 *
28 29 30
 * @FieldType(
 *   id = "entity_reference",
 *   label = @Translation("Entity reference"),
31
 *   description = @Translation("An entity field containing an entity reference."),
32
 *   category = @Translation("Reference"),
33
 *   no_ui = TRUE,
34
 *   list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
35
 *   constraints = {"ValidReference" = {}}
36
 * )
37 38 39
 */
class EntityReferenceItem extends FieldItemBase {

40 41 42 43 44
  /**
   * Marker value to identify a newly created entity.
   *
   * @var int
   */
45
  protected static $NEW_ENTITY_MARKER = -1;
46

47 48 49
  /**
   * {@inheritdoc}
   */
50
  public static function defaultStorageSettings() {
51 52 53
    return array(
      'target_type' => \Drupal::moduleHandler()->moduleExists('node') ? 'node' : 'user',
      'target_bundle' => NULL,
54
    ) + parent::defaultStorageSettings();
55 56 57 58 59
  }

  /**
   * {@inheritdoc}
   */
60
  public static function defaultFieldSettings() {
61 62
    return array(
      'handler' => 'default',
63
    ) + parent::defaultFieldSettings();
64 65
  }

66
  /**
67
   * {@inheritdoc}
68
   */
69
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
70 71
    $settings = $field_definition->getSettings();
    $target_type_info = \Drupal::entityManager()->getDefinition($settings['target_type']);
72

73
    if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
74 75 76
      // @todo: Lookup the entity type's ID data type and use it here.
      // https://drupal.org/node/2107249
      $target_id_definition = DataDefinition::create('integer')
77
        ->setLabel(t('@label ID', array($target_type_info->getLabel())))
78
        ->setSetting('unsigned', TRUE);
79
    }
80 81
    else {
      $target_id_definition = DataDefinition::create('string')
82
        ->setLabel(t('@label ID', array($target_type_info->getLabel())));
83
    }
84
    $target_id_definition->setRequired(TRUE);
85
    $properties['target_id'] = $target_id_definition;
86

87
    $properties['entity'] = DataReferenceDefinition::create('entity')
88
      ->setLabel($target_type_info->getLabel())
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
      ->setDescription(t('The referenced entity'))
      // The entity object is computed out of the entity ID.
      ->setComputed(TRUE)
      ->setReadOnly(FALSE)
      ->setTargetDefinition(EntityDataDefinition::create($settings['target_type']));

    if (isset($settings['target_bundle'])) {
      $properties['entity']->getTargetDefinition()->addConstraint('Bundle', $settings['target_bundle']);
    }

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function mainPropertyName() {
    return 'target_id';
107 108
  }

109 110 111
  /**
   * {@inheritdoc}
   */
112
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
113 114 115
    $target_type = $field_definition->getSetting('target_type');
    $target_type_info = \Drupal::entityManager()->getDefinition($target_type);

116
    if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
117 118 119 120 121 122 123 124 125 126 127 128 129
      $columns = array(
        'target_id' => array(
          'description' => 'The ID of the target entity.',
          'type' => 'int',
          'unsigned' => TRUE,
        ),
      );
    }
    else {
      $columns = array(
        'target_id' => array(
          'description' => 'The ID of the target entity.',
          'type' => 'varchar',
130 131 132
          // If the target entities act as bundles for another entity type,
          // their IDs should not exceed the maximum length for bundles.
          'length' => $target_type_info->getBundleOf() ? EntityTypeInterface::BUNDLE_MAX_LENGTH : 255,
133 134 135 136 137 138 139 140 141 142 143 144 145 146
        ),
      );
    }

    $schema = array(
      'columns' => $columns,
      'indexes' => array(
        'target_id' => array('target_id'),
      ),
    );

    return $schema;
  }

147
  /**
148
   * {@inheritdoc}
149 150 151
   */
  public function setValue($values, $notify = TRUE) {
    if (isset($values) && !is_array($values)) {
152 153 154
      // If either a scalar or an object was passed as the value for the item,
      // assign it to the 'entity' property since that works for both cases.
      $this->set('entity', $values, $notify);
155
    }
156
    else {
157 158 159
      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.
160
      if (isset($values['target_id']) && !isset($values['entity'])) {
161 162 163 164 165 166
        $this->onChange('target_id', FALSE);
      }
      elseif (!isset($values['target_id']) && isset($values['entity'])) {
        $this->onChange('entity', FALSE);
      }
      elseif (isset($values['target_id']) && isset($values['entity'])) {
167 168 169 170 171
        // 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_id will be different, due to the new entity
        // marker.
        $entity_id = $this->get('entity')->getTargetIdentifier();
172
        if ($entity_id != $values['target_id'] && ($values['target_id'] != static::$NEW_ENTITY_MARKER || !$this->entity->isNew())) {
173 174 175 176 177 178
          throw new \InvalidArgumentException('The target id 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());
179
      }
180
    }
181

182
  }
183

184 185 186
  /**
   * {@inheritdoc}
   */
187 188
  public function getValue() {
    $values = parent::getValue();
189 190 191

    // If there is an unsaved entity, return it as part of the field item values
    // to ensure idempotency of getValue() / setValue().
192
    if ($this->hasNewEntity()) {
193 194 195 196 197
      $values['entity'] = $this->entity;
    }
    return $values;
  }

198 199 200
  /**
   * {@inheritdoc}
   */
201
  public function onChange($property_name, $notify = TRUE) {
202
    // Make sure that the target ID and the target property stay in sync.
203 204
    if ($property_name == 'entity') {
      $property = $this->get('entity');
205
      $target_id = $property->isTargetNew() ? static::$NEW_ENTITY_MARKER : $property->getTargetIdentifier();
206
      $this->writePropertyValue('target_id', $target_id);
207
    }
208
    elseif ($property_name == 'target_id' && $this->target_id != static::$NEW_ENTITY_MARKER) {
209
      $this->writePropertyValue('entity', $this->target_id);
210
    }
211
    parent::onChange($property_name, $notify);
212
  }
213

214 215 216 217 218
  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    // Avoid loading the entity by first checking the 'target_id'.
219
    if ($this->target_id !== NULL) {
220 221
      return FALSE;
    }
222
    if ($this->entity && $this->entity instanceof EntityInterface) {
223 224 225 226 227 228 229 230 231
      return FALSE;
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function preSave() {
232
    if ($this->hasNewEntity()) {
233 234 235 236 237 238
      // 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.
239 240 241 242
      $this->target_id = $this->entity->id();
    }
  }

243 244 245 246
  /**
   * {@inheritdoc}
   */
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
247
    $manager = \Drupal::service('plugin.manager.entity_reference_selection');
248 249 250 251 252 253 254
    if ($referenceable = $manager->getSelectionHandler($field_definition)->getReferenceableEntities()) {
      $group = array_rand($referenceable);
      $values['target_id'] = array_rand($referenceable[$group]);
      return $values;
    }
  }

255 256 257 258 259 260 261 262 263 264
  /**
   * 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.
   */
265
  public function hasNewEntity() {
266
    return $this->target_id === static::$NEW_ENTITY_MARKER;
267
  }
268

269 270 271 272 273 274 275 276 277 278 279 280 281
  /**
   * {@inheritdoc}
   */
  public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
    $dependencies = [];
    if (is_array($field_definition->default_value) && count($field_definition->default_value)) {
      $target_entity_type = \Drupal::entityManager()->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type'));
      foreach ($field_definition->default_value as $default_value) {
        if (is_array($default_value) && isset($default_value['target_uuid'])) {
          $entity = \Drupal::entityManager()->loadEntityByUuid($target_entity_type->id(), $default_value['target_uuid']);
          // If the entity does not exist do not create the dependency.
          // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue()
          if ($entity) {
282
            $dependencies[$target_entity_type->getConfigDependencyKey()][] = $entity->getConfigDependencyName();
283 284 285 286 287 288 289
          }
        }
      }
    }
    return $dependencies;
  }

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
  /**
   * {@inheritdoc}
   */
  public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
    $changed = FALSE;
    if (!empty($field_definition->default_value)) {
      $target_entity_type = \Drupal::entityManager()->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type'));
      foreach ($field_definition->default_value as $key => $default_value) {
        if (is_array($default_value) && isset($default_value['target_uuid'])) {
          $entity = \Drupal::entityManager()->loadEntityByUuid($target_entity_type->id(), $default_value['target_uuid']);
          // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue()
          if ($entity && isset($dependencies[$entity->getConfigDependencyKey()][$entity->getConfigDependencyName()])) {
            unset($field_definition->default_value[$key]);
            $changed = TRUE;
          }
        }
      }
    }
    return $changed;
  }

311
}