Field.php 20.6 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
    // Ensure the field name is unique (we do not care about deleted fields).
320
    if ($prior_field = $storage_controller->load($this->id)) {
321
      $message = 'Attempt to create field name %name which already exists.';
322
      throw new FieldException(format_string($message, array('%name' => $this->name)));
323
    }
324

325 326 327 328
    // 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) {
329 330
      if (in_array($this->name, $info['entity_keys'])) {
        throw new FieldException(format_string('Attempt to create field %name which is reserved by entity type %type.', array('%name' => $this->name, '%type' => $type)));
331
      }
332 333 334
    }

    // Check that the field type is known.
335
    $field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
336 337 338
    if (!$field_type) {
      throw new FieldException(format_string('Attempt to create a field of unknown type %type.', array('%type' => $this->type)));
    }
339
    $this->module = $field_type['provider'];
340

341 342 343
    // 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'];
344

345 346
    // Notify the entity storage controller.
    $entity_manager->getStorageController($this->entity_type)->onFieldCreate($this);
347
  }
348

349
  /**
350
   * Prepares saving an updated field definition.
351
   *
352 353
   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
   *   The entity storage controller.
354
   */
355
  protected function preSaveUpdated(EntityStorageControllerInterface $storage_controller) {
356
    $module_handler = \Drupal::moduleHandler();
357
    $entity_manager = \Drupal::entityManager();
358
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
359

360
    // Some updates are always disallowed.
361
    if ($this->type != $this->original->type) {
362 363
      throw new FieldException("Cannot change an existing field's type.");
    }
364
    if ($this->entity_type != $this->original->entity_type) {
365
      throw new FieldException("Cannot change an existing field's entity_type.");
366 367
    }

368 369 370
    // 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);
371 372 373

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

376 377 378
    // 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.
379 380
    $entity_manager->getStorageController($this->entity_type)->onFieldUpdate($this);
  }
381

382 383 384 385 386
  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
    // Clear the cache.
387 388 389 390 391 392
    field_cache_clear();
  }

  /**
   * {@inheritdoc}
   */
393 394 395 396 397 398 399 400 401 402 403 404 405
  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}";
        }
406
      }
407 408 409 410 411 412
    }
    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;
413
      }
414 415
      $instance_controller->delete($instances);
    }
416

417 418 419 420 421 422 423 424 425 426 427 428 429
    // 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);
  }
430

431 432 433 434 435 436 437 438 439
  /**
   * {@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);
      }
440
    }
441 442 443

    // Clear the cache.
    field_cache_clear();
444 445 446
  }

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

      // Check that the schema does not include forbidden column names.
459
      if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
460 461 462 463 464 465 466 467 468 469 470 471 472
        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;
  }

473 474 475 476 477 478 479 480 481 482 483 484 485 486
  /**
   * {@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'];
  }

487
  /**
488
   * {@inheritdoc}
489 490 491 492
   */
  public function getBundles() {
    if (empty($this->deleted)) {
      $map = field_info_field_map();
493 494
      if (isset($map[$this->entity_type][$this->name]['bundles'])) {
        return $map[$this->entity_type][$this->name]['bundles'];
495 496 497 498 499
      }
    }
    return array();
  }

500 501 502
  /**
   * {@inheritdoc}
   */
503
  public function getName() {
504
    return $this->name;
505 506 507 508 509
  }

  /**
   * {@inheritdoc}
   */
510
  public function getType() {
511 512 513 514 515 516
    return $this->type;
  }

  /**
   * {@inheritdoc}
   */
517
  public function getSettings() {
518 519 520 521
    // @todo field_info_field_types() calls _field_info_collate_types() which
    //   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.
522
    $field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
523

524
    $settings = $this->settings + $field_type_info['settings'] + $field_type_info['instance_settings'];
525 526 527 528 529 530
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
531 532
  public function getSetting($setting_name) {
    // @todo See getSettings() about potentially statically caching this.
533
    $field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type);
534 535

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

  /**
   * {@inheritdoc}
   */
554
  public function getPropertyNames() {
555 556 557 558 559 560 561
    $schema = $this->getSchema();
    return array_keys($schema['columns']);
  }

  /**
   * {@inheritdoc}
   */
562
  public function isTranslatable() {
563 564 565
    return $this->translatable;
  }

566 567 568 569 570 571 572 573 574 575 576 577 578 579
  /**
   * 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;
  }

580 581 582
  /**
   * {@inheritdoc}
   */
583
  public function getLabel() {
584 585 586 587 588 589
    return $this->label();
  }

  /**
   * {@inheritdoc}
   */
590
  public function getDescription() {
591
    return NULL;
592 593 594 595 596
  }

  /**
   * {@inheritdoc}
   */
597
  public function getCardinality() {
598 599 600 601 602 603
    return $this->cardinality;
  }

  /**
   * {@inheritdoc}
   */
604
  public function isRequired() {
605 606 607
    return FALSE;
  }

608 609 610
  /**
   * {@inheritdoc}
   */
611 612
  public function isMultiple() {
    $cardinality = $this->getCardinality();
613 614 615
    return ($cardinality == static::CARDINALITY_UNLIMITED) || ($cardinality > 1);
  }

616 617 618
  /**
   * {@inheritdoc}
   */
619
  public function getDefaultValue(EntityInterface $entity) { }
620

621 622 623
  /**
   * {@inheritdoc}
   */
624
  public function isConfigurable() {
625 626 627 628 629 630
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
631
  public function isQueryable() {
632 633 634
    return TRUE;
  }

635 636 637 638 639 640 641 642
  /**
   * A list of columns that can not be used as field type columns.
   *
   * @return array
   */
  public static function getReservedColumns() {
    return array('deleted');
  }
643

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

    return FALSE;
  }
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699

  /**
   * 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);
  }

700 701 702
  /**
   * {@inheritdoc}
   */
703 704 705 706 707 708 709 710
  public function getDataType() {
    return 'list';
  }

  /**
   * {@inheritdoc}
   */
  public function isList() {
711 712 713
    return TRUE;
  }

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
  /**
   * {@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')
734
      ->getDefinition($this->getType());
735 736 737 738 739 740 741 742 743 744
    return $type_definition['list_class'];
  }

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

745 746 747 748 749 750 751
  /**
   * {@inheritdoc}
   */
  public function getConstraint($constraint_name) {
    return NULL;
  }

752 753 754 755 756 757
  /**
   * {@inheritdoc}
   */
  public function getItemDefinition() {
    if (!isset($this->itemDefinition)) {
      $this->itemDefinition = DataDefinition::create('field_item:' . $this->type)
758
        ->setSettings($this->getSettings());
759 760 761 762
    }
    return $this->itemDefinition;
  }

763
}