Field.php 20.3 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\field\Entity\Field.
6 7
 */

8
namespace Drupal\field\Entity;
9

10
use Drupal\Component\Utility\Unicode;
11
use Drupal\Core\Config\Entity\ConfigEntityBase;
12
use Drupal\Core\Entity\EntityInterface;
13
use Drupal\Core\Entity\EntityStorageControllerInterface;
14
use Drupal\Core\TypedData\DataDefinition;
15
use Drupal\field\FieldException;
16
use Drupal\field\FieldInterface;
17 18 19 20 21 22 23

/**
 * Defines the Field entity.
 *
 * @todo use 'field' as the id once hook_field_load() and friends
 * are removed.
 *
24
 * @EntityType(
25 26
 *   id = "field_entity",
 *   label = @Translation("Field"),
27
 *   controllers = {
28
 *     "storage" = "Drupal\field\FieldStorageController"
29
 *   },
30 31 32 33 34 35 36 37
 *   config_prefix = "field.field",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "id",
 *     "uuid" = "uuid"
 *   }
 * )
 */
38
class Field extends ConfigEntityBase implements FieldInterface {
39 40

  /**
41
   * The maximum length of the field name, in characters.
42 43 44
   *
   * For fields created through Field UI, this includes the 'field_' prefix.
   */
45
  const NAME_MAX_LENGTH = 32;
46 47

  /**
48 49 50 51 52 53 54 55 56 57 58 59
   * The field ID.
   *
   * The ID consists of 2 parts: the entity type and the field name.
   *
   * Example: node.body, user.field_main_image.
   *
   * @var string
   */
  public $id;

  /**
   * The field name.
60 61
   *
   * This is the name of the property under which the field values are placed in
62 63
   * an entity: $entity->{$field_name}. The maximum length is
   * Field:NAME_MAX_LENGTH.
64 65 66 67 68
   *
   * Example: body, field_main_image.
   *
   * @var string
   */
69
  public $name;
70 71 72 73 74 75 76 77 78 79

  /**
   * The field UUID.
   *
   * This is assigned automatically when the field is created.
   *
   * @var string
   */
  public $uuid;

80 81 82 83 84 85 86
  /**
   * The name of the entity type the field can be attached to.
   *
   * @var string
   */
  public $entity_type;

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  /**
   * The field type.
   *
   * Example: text, number_integer.
   *
   * @var string
   */
  public $type;

  /**
   * The name of the module that provides the field type.
   *
   * @var string
   */
  public $module;

  /**
   * Field-type specific settings.
   *
   * An array of key/value pairs, The keys and default values are defined by the
107
   * field type.
108 109 110
   *
   * @var array
   */
111
  public $settings = array();
112 113 114 115 116

  /**
   * The field cardinality.
   *
   * The maximum number of values the field can hold. Possible values are
117 118
   * positive integers or FieldDefinitionInterface::CARDINALITY_UNLIMITED.
   * Defaults to 1.
119 120 121
   *
   * @var integer
   */
122
  public $cardinality = 1;
123 124 125 126 127 128 129 130

  /**
   * Flag indicating whether the field is translatable.
   *
   * Defaults to FALSE.
   *
   * @var bool
   */
131
  public $translatable = FALSE;
132 133 134 135 136 137 138 139 140 141 142 143 144

  /**
   * Flag indicating whether the field is available for editing.
   *
   * If TRUE, some actions not available though the UI (but are still possible
   * through direct API manipulation):
   * - field settings cannot be changed,
   * - new instances cannot be created
   * - existing instances cannot be deleted.
   * Defaults to FALSE.
   *
   * @var bool
   */
145
  public $locked = FALSE;
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

  /**
   * The custom storage indexes for the field data storage.
   *
   * This set of indexes is merged with the "default" indexes specified by the
   * field type in hook_field_schema() to determine the actual set of indexes
   * that get created.
   *
   * The indexes are defined using the same definition format as Schema API
   * index specifications. Only columns that are part of the field schema, as
   * defined by the field type in hook_field_schema(), are allowed.
   *
   * Some storage backends might not support indexes, and discard that
   * information.
   *
   * @var array
   */
163
  public $indexes = array();
164 165 166 167 168 169 170 171 172 173 174 175 176 177

  /**
   * Flag indicating whether the field is deleted.
   *
   * The delete() method marks the field as "deleted" and removes the
   * corresponding entry from the config storage, but keeps its definition in
   * the state storage while field data is purged by a separate
   * garbage-collection process.
   *
   * Deleted fields stay out of the regular entity lifecycle (notably, their
   * values are not populated in loaded entities, and are not saved back).
   *
   * @var bool
   */
178
  public $deleted = FALSE;
179 180 181 182 183 184 185 186

  /**
   * The field schema.
   *
   * @var array
   */
  protected $schema;

187 188 189
  /**
   * The original field.
   *
190
   * @var \Drupal\field\Entity\Field
191 192 193
   */
  public $original = NULL;

194 195 196 197 198 199 200
  /**
   * The data definition of a field item.
   *
   * @var \Drupal\Core\TypedData\DataDefinition
   */
  protected $itemDefinition;

201
  /**
202 203 204 205 206 207 208
   * Constructs a Field object.
   *
   * @param array $values
   *   An array of field properties, keyed by property name. Most array
   *   elements will be used to set the corresponding properties on the class;
   *   see the class property documentation for details. Some array elements
   *   have special meanings and a few are required. Special elements are:
209
   *   - name: required. As a temporary Backwards Compatibility layer right now,
210
   *     a 'field_name' property can be accepted in place of 'id'.
211
   *   - entity_type: required.
212 213 214 215 216 217 218 219 220
   *   - type: required.
   *
   * In most cases, Field entities are created via
   * entity_create('field_entity', $values)), where $values is the same
   * parameter as in this constructor.
   *
   * @see entity_create()
   *
   * @ingroup field_crud
221 222 223
   */
  public function __construct(array $values, $entity_type = 'field_entity') {
    // Check required properties.
224
    if (empty($values['name'])) {
225 226
      throw new FieldException('Attempt to create an unnamed field.');
    }
227
    if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['name'])) {
228 229
      throw new FieldException('Attempt to create a field with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character');
    }
230 231 232 233 234 235
    if (empty($values['type'])) {
      throw new FieldException('Attempt to create a field with no type.');
    }
    if (empty($values['entity_type'])) {
      throw new FieldException('Attempt to create a field with no entity_type.');
    }
236 237 238 239

    parent::__construct($values, $entity_type);
  }

240 241 242 243 244 245 246
  /**
   * {@inheritdoc}
   */
  public function id() {
    return $this->entity_type . '.' . $this->name;
  }

247 248 249 250 251 252 253 254 255
  /**
   * {@inheritdoc}
   */
  public function getExportProperties() {
    $names = array(
      'id',
      'uuid',
      'status',
      'langcode',
256 257
      'name',
      'entity_type',
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
      'type',
      'settings',
      'module',
      'locked',
      'cardinality',
      'translatable',
      'indexes',
    );
    $properties = array();
    foreach ($names as $name) {
      $properties[$name] = $this->get($name);
    }
    return $properties;
  }

  /**
274
   * Overrides \Drupal\Core\Entity\Entity::preSave().
275 276 277 278 279
   *
   * @throws \Drupal\field\FieldException
   *   If the field definition is invalid.
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   In case of failures at the configuration storage level.
280
   */
281
  public function preSave(EntityStorageControllerInterface $storage_controller) {
282
    // Clear the derived data about the field.
283
    unset($this->schema);
284 285

    if ($this->isNew()) {
286
      return $this->preSaveNew($storage_controller);
287 288
    }
    else {
289
      return $this->preSaveUpdated($storage_controller);
290 291
    }
  }
292

293
  /**
294
   * Prepares saving a new field definition.
295
   *
296 297
   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
   *   The entity storage controller.
298
   *
299
   * @throws \Drupal\field\FieldException If the field definition is invalid.
300
   */
301
   protected function preSaveNew(EntityStorageControllerInterface $storage_controller) {
302 303
    $entity_manager = \Drupal::entityManager();

304 305 306 307 308
    // Assign the ID.
    $this->id = $this->id();

    // Field name cannot be longer than Field::NAME_MAX_LENGTH characters. We
    // use Unicode::strlen() because the DB layer assumes that column widths
309
    // are given in characters rather than bytes.
310
    if (Unicode::strlen($this->name) > static::NAME_MAX_LENGTH) {
311
      throw new FieldException(format_string(
312 313 314
        'Attempt to create a field with an ID longer than @max characters: %name', array(
          '@max' => static::NAME_MAX_LENGTH,
          '%name' => $this->name,
315 316 317
        )
      ));
    }
318

319 320 321 322
    // Disallow reserved field names. This can't prevent all field name
    // collisions with existing entity properties, but some is better than
    // none.
    foreach ($entity_manager->getDefinitions() as $type => $info) {
323
      if (in_array($this->name, $info->getKeys())) {
324
        throw new FieldException(format_string('Attempt to create field %name which is reserved by entity type %type.', array('%name' => $this->name, '%type' => $type)));
325
      }
326 327 328
    }

    // Check that the field type is known.
329
    $field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
330 331 332
    if (!$field_type) {
      throw new FieldException(format_string('Attempt to create a field of unknown type %type.', array('%type' => $this->type)));
    }
333
    $this->module = $field_type['provider'];
334

335 336 337
    // Make sure all settings are present, so that a complete field
    // definition is passed to the various hooks and written to config.
    $this->settings += $field_type['settings'];
338

339 340
    // Notify the entity storage controller.
    $entity_manager->getStorageController($this->entity_type)->onFieldCreate($this);
341
  }
342

343
  /**
344
   * Prepares saving an updated field definition.
345
   *
346 347
   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
   *   The entity storage controller.
348
   */
349
  protected function preSaveUpdated(EntityStorageControllerInterface $storage_controller) {
350
    $module_handler = \Drupal::moduleHandler();
351
    $entity_manager = \Drupal::entityManager();
352
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
353

354
    // Some updates are always disallowed.
355
    if ($this->type != $this->original->type) {
356 357
      throw new FieldException("Cannot change an existing field's type.");
    }
358
    if ($this->entity_type != $this->original->entity_type) {
359
      throw new FieldException("Cannot change an existing field's entity_type.");
360 361
    }

362 363 364
    // Make sure all settings are present, so that a complete field
    // definition is passed to the various hooks and written to config.
    $this->settings += $field_type_manager->getDefaultSettings($this->type);
365 366 367

    // See if any module forbids the update by throwing an exception. This
    // invokes hook_field_update_forbid().
368
    $module_handler->invokeAll('field_update_forbid', array($this, $this->original));
369

370 371 372
    // Notify the storage controller. The controller can reject the definition
    // update as invalid by raising an exception, which stops execution before
    // the definition is written to config.
373 374
    $entity_manager->getStorageController($this->entity_type)->onFieldUpdate($this);
  }
375

376 377 378 379 380
  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
    // Clear the cache.
381 382 383 384 385 386
    field_cache_clear();
  }

  /**
   * {@inheritdoc}
   */
387 388 389 390 391 392 393 394 395 396 397 398 399
  public static function preDelete(EntityStorageControllerInterface $storage_controller, array $fields) {
    $state = \Drupal::state();
    $instance_controller = \Drupal::entityManager()->getStorageController('field_instance');

    // Delete instances first. Note: when deleting a field through
    // FieldInstance::postDelete(), the instances have been deleted already, so
    // no instances will be found here.
    $instance_ids = array();
    foreach ($fields as $field) {
      if (!$field->deleted) {
        foreach ($field->getBundles() as $bundle) {
          $instance_ids[] = "{$field->entity_type}.$bundle.{$field->name}";
        }
400
      }
401 402 403 404 405 406
    }
    if ($instance_ids) {
      $instances = $instance_controller->loadMultiple($instance_ids);
      // Tag the objects to preserve recursive deletion of the field.
      foreach ($instances as $instance) {
        $instance->noFieldDelete = TRUE;
407
      }
408 409
      $instance_controller->delete($instances);
    }
410

411 412 413 414 415 416 417 418 419 420 421 422 423
    // Keep the field definitions in the state storage so we can use them later
    // during field_purge_batch().
    $deleted_fields = $state->get('field.field.deleted') ?: array();
    foreach ($fields as $field) {
      if (!$field->deleted) {
        $config = $field->getExportProperties();
        $config['deleted'] = TRUE;
        $config['bundles'] = $field->getBundles();
        $deleted_fields[$field->uuid] = $config;
      }
    }
    $state->set('field.field.deleted', $deleted_fields);
  }
424

425 426 427 428 429 430 431 432 433
  /**
   * {@inheritdoc}
   */
  public static function postDelete(EntityStorageControllerInterface $storage_controller, array $fields) {
    // Notify the storage.
    foreach ($fields as $field) {
      if (!$field->deleted) {
        \Drupal::entityManager()->getStorageController($field->entity_type)->onFieldDelete($field);
      }
434
    }
435 436 437

    // Clear the cache.
    field_cache_clear();
438 439 440
  }

  /**
441
   * {@inheritdoc}
442 443 444
   */
  public function getSchema() {
    if (!isset($this->schema)) {
445
      // Get the schema from the field item class.
446
      $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
447 448 449 450
      $class = $definition['class'];
      $schema = $class::schema($this);
      // Fill in default values for optional entries.
      $schema += array('indexes' => array(), 'foreign keys' => array());
451 452

      // Check that the schema does not include forbidden column names.
453
      if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
454 455 456 457 458 459 460 461 462 463 464 465 466
        throw new FieldException('Illegal field type columns.');
      }

      // Merge custom indexes with those specified by the field type. Custom
      // indexes prevail.
      $schema['indexes'] = $this->indexes + $schema['indexes'];

      $this->schema = $schema;
    }

    return $this->schema;
  }

467 468 469 470 471 472 473 474 475 476 477 478 479 480
  /**
   * {@inheritdoc}
   */
  public function getColumns() {
    $schema = $this->getSchema();
    // A typical use case for the method is to iterate on the columns, while
    // some other use cases rely on identifying the first column with the key()
    // function. Since the schema is persisted in the Field object, we take care
    // of resetting the array pointer so that the former does not interfere with
    // the latter.
    reset($schema['columns']);
    return $schema['columns'];
  }

481
  /**
482
   * {@inheritdoc}
483 484 485 486
   */
  public function getBundles() {
    if (empty($this->deleted)) {
      $map = field_info_field_map();
487 488
      if (isset($map[$this->entity_type][$this->name]['bundles'])) {
        return $map[$this->entity_type][$this->name]['bundles'];
489 490 491 492 493
      }
    }
    return array();
  }

494 495 496
  /**
   * {@inheritdoc}
   */
497
  public function getName() {
498
    return $this->name;
499 500 501 502 503
  }

  /**
   * {@inheritdoc}
   */
504
  public function getType() {
505 506 507 508 509 510
    return $this->type;
  }

  /**
   * {@inheritdoc}
   */
511
  public function getSettings() {
512 513 514 515
    // @todo FieldTypePluginManager maintains its own static cache. However, do
    //   some CPU and memory profiling to see if it's worth statically caching
    //   $field_type_info, or the default field and instance settings, within
    //   $this.
516
    $field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
517

518
    $settings = $this->settings + $field_type_info['settings'] + $field_type_info['instance_settings'];
519 520 521 522 523 524
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
525 526
  public function getSetting($setting_name) {
    // @todo See getSettings() about potentially statically caching this.
527
    $field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
528 529

    // We assume here that consecutive array_key_exists() is more efficient than
530
    // calling getSettings() when all we need is a single setting.
531
    if (array_key_exists($setting_name, $this->settings)) {
532 533
      return $this->settings[$setting_name];
    }
534
    elseif (array_key_exists($setting_name, $field_type_info['settings'])) {
535 536
      return $field_type_info['settings'][$setting_name];
    }
537
    elseif (array_key_exists($setting_name, $field_type_info['instance_settings'])) {
538 539
      return $field_type_info['instance_settings'][$setting_name];
    }
540 541 542
    else {
      return NULL;
    }
543 544 545 546 547
  }

  /**
   * {@inheritdoc}
   */
548
  public function getPropertyNames() {
549 550 551 552 553 554 555
    $schema = $this->getSchema();
    return array_keys($schema['columns']);
  }

  /**
   * {@inheritdoc}
   */
556
  public function isTranslatable() {
557 558 559
    return $this->translatable;
  }

560 561 562 563 564 565 566 567 568 569 570 571 572 573
  /**
   * Sets whether the field is translatable.
   *
   * @param bool $translatable
   *   Whether the field is translatable.
   *
   * @return \Drupal\field\Entity\Field
   *   The object itself for chaining.
   */
  public function setTranslatable($translatable) {
    $this->translatable = $translatable;
    return $this;
  }

574 575 576
  /**
   * {@inheritdoc}
   */
577
  public function getLabel() {
578 579 580 581 582 583
    return $this->label();
  }

  /**
   * {@inheritdoc}
   */
584
  public function getDescription() {
585
    return NULL;
586 587 588 589 590
  }

  /**
   * {@inheritdoc}
   */
591
  public function getCardinality() {
592 593 594 595 596 597
    return $this->cardinality;
  }

  /**
   * {@inheritdoc}
   */
598
  public function isRequired() {
599 600 601
    return FALSE;
  }

602 603 604
  /**
   * {@inheritdoc}
   */
605 606
  public function isMultiple() {
    $cardinality = $this->getCardinality();
607 608 609
    return ($cardinality == static::CARDINALITY_UNLIMITED) || ($cardinality > 1);
  }

610 611 612
  /**
   * {@inheritdoc}
   */
613
  public function getDefaultValue(EntityInterface $entity) { }
614

615 616 617
  /**
   * {@inheritdoc}
   */
618
  public function isConfigurable() {
619 620 621 622 623 624
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
625
  public function isQueryable() {
626 627 628
    return TRUE;
  }

629 630 631 632 633 634 635 636
  /**
   * A list of columns that can not be used as field type columns.
   *
   * @return array
   */
  public static function getReservedColumns() {
    return array('deleted');
  }
637

638 639 640 641 642 643 644
  /**
   * Determines whether a field has any data.
   *
   * @return
   *   TRUE if the field has data for any entity; FALSE otherwise.
   */
  public function hasData() {
645 646 647 648
    if ($this->getBundles()) {
      $storage_details = $this->getSchema();
      $columns = array_keys($storage_details['columns']);
      $factory = \Drupal::service('entity.query');
649
      // Entity Query throws an exception if there is no base table.
650
      $entity_info = \Drupal::entityManager()->getDefinition($this->entity_type);
651
      if (!$entity_info->getBaseTable()) {
652
        return FALSE;
653
      }
654
      $query = $factory->get($this->entity_type);
655 656
      $group = $query->orConditionGroup();
      foreach ($columns as $column) {
657
        $group->exists($this->name . '.' . $column);
658 659 660 661 662 663 664 665 666 667 668 669 670 671
      }
      $result = $query
        ->condition($group)
        ->count()
        ->accessCheck(FALSE)
        ->range(0, 1)
        ->execute();
      if ($result) {
        return TRUE;
      }
    }

    return FALSE;
  }
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693

  /**
   * Implements the magic __sleep() method.
   *
   * Using the Serialize interface and serialize() / unserialize() methods
   * breaks entity forms in PHP 5.4.
   * @todo Investigate in https://drupal.org/node/2074253.
   */
  public function __sleep() {
    // Only serialize properties from getExportProperties().
    return array_keys(array_intersect_key($this->getExportProperties(), get_object_vars($this)));
  }

  /**
   * Implements the magic __wakeup() method.
   */
  public function __wakeup() {
    // Run the values from getExportProperties() through __construct().
    $values = array_intersect_key($this->getExportProperties(), get_object_vars($this));
    $this->__construct($values);
  }

694 695 696
  /**
   * {@inheritdoc}
   */
697 698 699 700 701 702 703 704
  public function getDataType() {
    return 'list';
  }

  /**
   * {@inheritdoc}
   */
  public function isList() {
705 706 707
    return TRUE;
  }

708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
  /**
   * {@inheritdoc}
   */
  public function isReadOnly() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function isComputed() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getClass() {
    // Derive list class from the field type.
    $type_definition = \Drupal::service('plugin.manager.field.field_type')
728
      ->getDefinition($this->getType());
729 730 731 732 733 734 735 736 737 738
    return $type_definition['list_class'];
  }

  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    return array();
  }

739 740 741 742 743 744 745
  /**
   * {@inheritdoc}
   */
  public function getConstraint($constraint_name) {
    return NULL;
  }

746 747 748 749 750 751
  /**
   * {@inheritdoc}
   */
  public function getItemDefinition() {
    if (!isset($this->itemDefinition)) {
      $this->itemDefinition = DataDefinition::create('field_item:' . $this->type)
752
        ->setSettings($this->getSettings());
753 754 755 756
    }
    return $this->itemDefinition;
  }

757
}