FieldStorageConfig.php 21.4 KB
Newer Older
1 2 3 4
<?php

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

8
namespace Drupal\field\Entity;
9

10
use Drupal\Component\Utility\String;
11
use Drupal\Component\Utility\Unicode;
12
use Drupal\Core\Config\Entity\ConfigEntityBase;
13
use Drupal\Core\Entity\EntityStorageInterface;
14
use Drupal\Core\Entity\FieldableEntityInterface;
15
use Drupal\Core\Field\FieldException;
16
use Drupal\Core\Field\FieldStorageDefinitionInterface;
17
use Drupal\Core\TypedData\OptionsProviderInterface;
18
use Drupal\field\FieldStorageConfigInterface;
19 20

/**
21
 * Defines the Field storage configuration entity.
22
 *
23
 * @ConfigEntityType(
24
 *   id = "field_storage_config",
25
 *   label = @Translation("Field storage"),
26
 *   handlers = {
27
 *     "storage" = "Drupal\field\FieldStorageConfigStorage"
28
 *   },
29
 *   config_prefix = "storage",
30 31
 *   entity_keys = {
 *     "id" = "id",
32
 *     "label" = "id"
33 34 35
 *   }
 * )
 */
36
class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigInterface {
37 38

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

  /**
46 47 48 49 50 51 52 53 54 55 56 57
   * 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.
58 59
   *
   * This is the name of the property under which the field values are placed in
60 61
   * an entity: $entity->{$field_name}. The maximum length is
   * Field:NAME_MAX_LENGTH.
62 63 64 65 66
   *
   * Example: body, field_main_image.
   *
   * @var string
   */
67
  public $field_name;
68

69 70 71 72 73 74 75
  /**
   * The name of the entity type the field can be attached to.
   *
   * @var string
   */
  public $entity_type;

76 77 78
  /**
   * The field type.
   *
79
   * Example: text, integer.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
   *
   * @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
96
   * field type.
97 98 99
   *
   * @var array
   */
100
  public $settings = array();
101 102 103 104 105

  /**
   * The field cardinality.
   *
   * The maximum number of values the field can hold. Possible values are
106 107
   * positive integers or
   * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
108
   *
109
   * @var int
110
   */
111
  public $cardinality = 1;
112 113 114 115

  /**
   * Flag indicating whether the field is translatable.
   *
116
   * Defaults to TRUE.
117 118 119
   *
   * @var bool
   */
120
  public $translatable = TRUE;
121 122 123 124 125 126 127

  /**
   * 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,
128 129
   * - new fields cannot be created
   * - existing fields cannot be deleted.
130 131 132 133
   * Defaults to FALSE.
   *
   * @var bool
   */
134
  public $locked = FALSE;
135

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
  /**
   * Flag indicating whether the field storage should be deleted when orphaned.
   *
   * By default field storages for configurable fields are removed when there
   * are no remaining fields using them. If multiple modules provide bundles
   * which need to use the same field storage then setting this to TRUE will
   * preserve the field storage regardless of what happens to the bundles. The
   * classic use case for this is node body field storage since Book, Forum, the
   * Standard profile and bundle (node type) creation through the UI all use
   * same field storage.
   *
   * @var bool
   */
  protected $persist_with_no_fields = FALSE;

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  /**
   * 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
   */
167
  public $indexes = array();
168 169 170 171 172 173 174 175 176 177 178 179 180 181

  /**
   * 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
   */
182
  public $deleted = FALSE;
183 184 185 186 187 188 189 190

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

191 192 193 194 195 196 197 198 199
  /**
   * An array of field property definitions.
   *
   * @var \Drupal\Core\TypedData\DataDefinitionInterface[]
   *
   * @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
   */
  protected $propertyDefinitions;

200
  /**
201
   * Constructs a FieldStorageConfig object.
202 203 204 205 206 207
   *
   * @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:
208
   *   - name: required. As a temporary Backwards Compatibility layer right now,
209
   *     a 'field_name' property can be accepted in place of 'id'.
210
   *   - entity_type: required.
211 212 213
   *   - type: required.
   *
   * In most cases, Field entities are created via
214
   * entity_create('field_storage_config', $values)), where $values is the same
215 216 217
   * parameter as in this constructor.
   *
   * @see entity_create()
218
   */
219
  public function __construct(array $values, $entity_type = 'field_storage_config') {
220
    // Check required properties.
221 222
    if (empty($values['field_name'])) {
      throw new FieldException('Attempt to create a field storage without a field name.');
223
    }
224 225
    if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['field_name'])) {
      throw new FieldException(String::format('Attempt to create a field storage @field_name with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character', array('@field_name' => $values['field_name'])));
226
    }
227
    if (empty($values['type'])) {
228
      throw new FieldException(String::format('Attempt to create a field storage @field_name with no type.', array('@field_name' => $values['field_name'])));
229 230
    }
    if (empty($values['entity_type'])) {
231
      throw new FieldException(String::format('Attempt to create a field storage @field_name with no entity_type.', array('@field_name' => $values['field_name'])));
232
    }
233 234 235 236

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

237 238 239 240
  /**
   * {@inheritdoc}
   */
  public function id() {
241
    return $this->entity_type . '.' . $this->field_name;
242 243
  }

244
  /**
245
   * Overrides \Drupal\Core\Entity\Entity::preSave().
246
   *
247
   * @throws \Drupal\Core\Field\FieldException
248 249 250
   *   If the field definition is invalid.
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   In case of failures at the configuration storage level.
251
   */
252
  public function preSave(EntityStorageInterface $storage) {
253
    // Clear the derived data about the field.
254
    unset($this->schema);
255

256 257 258 259 260 261 262
    // Filter out unknown settings and make sure all settings are present, so
    // that a complete field definition is passed to the various hooks and
    // written to config.
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
    $default_settings = $field_type_manager->getDefaultStorageSettings($this->type);
    $this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;

263
    if ($this->isNew()) {
264
      $this->preSaveNew($storage);
265 266
    }
    else {
267
      $this->preSaveUpdated($storage);
268
    }
269 270

    parent::preSave($storage);
271
  }
272

273
  /**
274
   * Prepares saving a new field definition.
275
   *
276 277
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
278
   *
279
   * @throws \Drupal\Core\Field\FieldException If the field definition is invalid.
280
   */
281
  protected function preSaveNew(EntityStorageInterface $storage) {
282
    $entity_manager = \Drupal::entityManager();
283
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
284

285 286 287
    // Assign the ID.
    $this->id = $this->id();

288
    // Field name cannot be longer than FieldStorageConfig::NAME_MAX_LENGTH characters.
289
    // We use Unicode::strlen() because the DB layer assumes that column widths
290
    // are given in characters rather than bytes.
291
    if (Unicode::strlen($this->field_name) > static::NAME_MAX_LENGTH) {
292
      throw new FieldException(String::format(
293
        'Attempt to create a field storage with an name longer than @max characters: %name', array(
294
          '@max' => static::NAME_MAX_LENGTH,
295
          '%name' => $this->field_name,
296 297 298
        )
      ));
    }
299

300 301
    // Disallow reserved field names.
    $disallowed_field_names = array_keys($entity_manager->getBaseFieldDefinitions($this->entity_type));
302 303
    if (in_array($this->field_name, $disallowed_field_names)) {
      throw new FieldException(String::format('Attempt to create field storage %name which is reserved by entity type %type.', array('%name' => $this->field_name, '%type' => $this->entity_type)));
304 305 306
    }

    // Check that the field type is known.
307
    $field_type = $field_type_manager->getDefinition($this->type, FALSE);
308
    if (!$field_type) {
309
      throw new FieldException(String::format('Attempt to create a field storage of unknown type %type.', array('%type' => $this->type)));
310
    }
311
    $this->module = $field_type['provider'];
312

313 314
    // Notify the entity manager.
    $entity_manager->onFieldStorageDefinitionCreate($this);
315
  }
316

317 318 319 320 321 322 323
  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    parent::calculateDependencies();
    // Ensure the field is dependent on the providing module.
    $this->addDependency('module', $this->module);
324 325 326
    // Ensure the field is dependent on the provider of the entity type.
    $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
    $this->addDependency('module', $entity_type->getProvider());
327 328 329
    return $this->dependencies;
  }

330
  /**
331
   * Prepares saving an updated field definition.
332
   *
333 334
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
335
   */
336
  protected function preSaveUpdated(EntityStorageInterface $storage) {
337
    $module_handler = \Drupal::moduleHandler();
338
    $entity_manager = \Drupal::entityManager();
339

340
    // Some updates are always disallowed.
341
    if ($this->type != $this->original->type) {
342
      throw new FieldException("Cannot change the field type for an existing field storage.");
343
    }
344
    if ($this->entity_type != $this->original->entity_type) {
345
      throw new FieldException("Cannot change the entity type for an existing field storage.");
346 347 348
    }

    // See if any module forbids the update by throwing an exception. This
349 350
    // invokes hook_field_storage_config_update_forbid().
    $module_handler->invokeAll('field_storage_config_update_forbid', array($this, $this->original));
351

352
    // Notify the entity manager. A listener can reject the definition
353 354
    // update as invalid by raising an exception, which stops execution before
    // the definition is written to config.
355
    $entity_manager->onFieldStorageDefinitionUpdate($this, $this->original);
356
  }
357

358 359 360
  /**
   * {@inheritdoc}
   */
361
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
362 363 364 365
    if ($update) {
      // Invalidate the render cache for all affected entities.
      $entity_manager = \Drupal::entityManager();
      $entity_type = $this->getTargetEntityTypeId();
366
      if ($entity_manager->hasHandler($entity_type, 'view_builder')) {
367 368 369
        $entity_manager->getViewBuilder($entity_type)->resetCache();
      }
    }
370 371 372 373 374
  }

  /**
   * {@inheritdoc}
   */
375
  public static function preDelete(EntityStorageInterface $storage, array $field_storages) {
376
    $state = \Drupal::state();
377 378 379 380 381 382 383 384 385
    $field_config_storage = \Drupal::entityManager()->getStorage('field_config');

    // Delete fields first. Note: when deleting a field storage through
    // FieldConfig::postDelete(), the fields have been deleted already, so
    // no fields will be found here.
    $field_ids = array();
    foreach ($field_storages as $field_storage) {
      if (!$field_storage->deleted) {
        foreach ($field_storage->getBundles() as $bundle) {
386
          $field_ids[] = "{$field_storage->entity_type}.$bundle.{$field_storage->field_name}";
387
        }
388
      }
389
    }
390 391
    if ($field_ids) {
      $fields = $field_config_storage->loadMultiple($field_ids);
392
      // Tag the objects to preserve recursive deletion of the field.
393 394
      foreach ($fields as $field) {
        $field->noFieldDelete = TRUE;
395
      }
396
      $field_config_storage->delete($fields);
397
    }
398

399 400
    // Keep the field definitions in the state storage so we can use them later
    // during field_purge_batch().
401
    $deleted_storages = $state->get('field.storage.deleted') ?: array();
402 403 404
    foreach ($field_storages as $field_storage) {
      if (!$field_storage->deleted) {
        $config = $field_storage->toArray();
405
        $config['deleted'] = TRUE;
406 407
        $config['bundles'] = $field_storage->getBundles();
        $deleted_storages[$field_storage->uuid()] = $config;
408 409
      }
    }
410

411
    $state->set('field.storage.deleted', $deleted_storages);
412
  }
413

414 415 416
  /**
   * {@inheritdoc}
   */
417
  public static function postDelete(EntityStorageInterface $storage, array $fields) {
418 419 420
    // Notify the storage.
    foreach ($fields as $field) {
      if (!$field->deleted) {
421
        \Drupal::entityManager()->onFieldStorageDefinitionDelete($field);
422
        $field->deleted = TRUE;
423
      }
424 425 426 427
    }
  }

  /**
428
   * {@inheritdoc}
429 430 431
   */
  public function getSchema() {
    if (!isset($this->schema)) {
432
      // Get the schema from the field item class.
433
      $class = $this->getFieldItemClass();
434 435
      $schema = $class::schema($this);
      // Fill in default values for optional entries.
436 437 438 439 440
      $schema += array(
        'unique keys' => array(),
        'indexes' => array(),
        'foreign keys' => array(),
      );
441 442 443 444 445 446 447 448 449 450 451

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

452 453 454 455 456 457 458
  /**
   * {@inheritdoc}
   */
  public function hasCustomStorage() {
    return FALSE;
  }

459 460 461 462 463 464 465
  /**
   * {@inheritdoc}
   */
  public function isBaseField() {
    return FALSE;
  }

466 467 468 469 470 471 472 473 474 475 476 477 478 479
  /**
   * {@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'];
  }

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

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

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

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

517
    $settings = $field_type_manager->getDefaultStorageSettings($this->type);
518
    return $this->settings + $settings;
519 520 521 522 523
  }

  /**
   * {@inheritdoc}
   */
524 525
  public function getSetting($setting_name) {
    // @todo See getSettings() about potentially statically caching this.
526 527
    // We assume here that one call to array_key_exists() is more efficient
    // than calling getSettings() when all we need is a single setting.
528
    if (array_key_exists($setting_name, $this->settings)) {
529 530
      return $this->settings[$setting_name];
    }
531 532 533
    $settings = $this->getSettings();
    if (array_key_exists($setting_name, $settings)) {
      return $settings[$setting_name];
534
    }
535 536 537
    else {
      return NULL;
    }
538 539 540 541 542
  }

  /**
   * {@inheritdoc}
   */
543
  public function isTranslatable() {
544 545 546
    return $this->translatable;
  }

547 548 549 550 551 552 553 554
  /**
   * {@inheritdoc}
   */
  public function isRevisionable() {
    // All configurable fields are revisionable.
    return TRUE;
  }

555
  /**
556
   * {@inheritdoc}
557 558 559 560 561 562
   */
  public function setTranslatable($translatable) {
    $this->translatable = $translatable;
    return $this;
  }

563 564 565 566 567 568 569
  /**
   * {@inheritdoc}
   */
  public function getProvider() {
    return 'field';
  }

570 571 572
  /**
   * {@inheritdoc}
   */
573
  public function getLabel() {
574 575 576 577 578 579
    return $this->label();
  }

  /**
   * {@inheritdoc}
   */
580
  public function getDescription() {
581
    return NULL;
582 583 584 585 586
  }

  /**
   * {@inheritdoc}
   */
587
  public function getCardinality() {
588 589 590 591 592 593
    return $this->cardinality;
  }

  /**
   * {@inheritdoc}
   */
594
  public function isRequired() {
595 596 597
    return FALSE;
  }

598 599 600
  /**
   * {@inheritdoc}
   */
601
  public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
602 603 604 605 606 607 608 609 610
    // If the field item class implements the interface, proxy it through.
    $item = $entity->get($this->getName())->first();
    if ($item instanceof OptionsProviderInterface) {
      return $item;
    }
    // @todo: Allow setting custom options provider, see
    // https://www.drupal.org/node/2002138.
  }

611 612 613
  /**
   * {@inheritdoc}
   */
614 615
  public function isMultiple() {
    $cardinality = $this->getCardinality();
616
    return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
617 618
  }

619 620 621 622 623 624 625
  /**
   * {@inheritdoc}
   */
  public function isLocked() {
    return $this->locked;
  }

626 627
  /**
   * {@inheritdoc}
628 629 630 631 632 633 634
   */
  public function getTargetEntityTypeId() {
    return $this->entity_type;
  }

  /**
   * {@inheritdoc}
635
   */
636
  public function isQueryable() {
637 638 639
    return TRUE;
  }

640 641 642
  /**
   * Determines whether a field has any data.
   *
643
   * @return bool
644 645 646
   *   TRUE if the field has data for any entity; FALSE otherwise.
   */
  public function hasData() {
647
    return \Drupal::entityManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
648
  }
649 650 651 652 653 654 655 656 657

  /**
   * 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() {
658 659 660 661 662
    // Only serialize necessary properties, excluding those that can be
    // recalculated.
    $properties = get_object_vars($this);
    unset($properties['schema'], $properties['propertyDefinitions']);
    return array_keys($properties);
663 664
  }

665 666 667 668 669 670 671
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    return array();
  }

672 673 674 675 676 677 678
  /**
   * {@inheritdoc}
   */
  public function getConstraint($constraint_name) {
    return NULL;
  }

679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinition($name) {
    if (!isset($this->propertyDefinitions)) {
      $this->getPropertyDefinitions();
    }
    if (isset($this->propertyDefinitions[$name])) {
      return $this->propertyDefinitions[$name];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinitions() {
    if (!isset($this->propertyDefinitions)) {
      $class = $this->getFieldItemClass();
      $this->propertyDefinitions = $class::propertyDefinitions($this);
    }
    return $this->propertyDefinitions;
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyNames() {
    return array_keys($this->getPropertyDefinitions());
  }

  /**
   * {@inheritdoc}
   */
  public function getMainPropertyName() {
    $class = $this->getFieldItemClass();
    return $class::mainPropertyName();
  }

717 718 719 720 721 722 723
  /**
   * {@inheritdoc}
   */
  public function getUniqueStorageIdentifier() {
    return $this->uuid();
  }

724 725 726 727
  /**
   * Helper to retrieve the field item class.
   */
  protected function getFieldItemClass() {
728 729 730
    $type_definition = \Drupal::typedDataManager()
      ->getDefinition('field_item:' . $this->getType());
    return $type_definition['class'];
731 732
  }

733 734 735 736 737 738 739 740 741 742 743 744 745
  /**
   * Loads a field config entity based on the entity type and field name.
   *
   * @param string $entity_type_id
   *   ID of the entity type.
   * @param string $field_name
   *   Name of the field.
   *
   * @return static
   *   The field config entity if one exists for the provided field name,
   *   otherwise NULL.
   */
  public static function loadByName($entity_type_id, $field_name) {
746
    return \Drupal::entityManager()->getStorage('field_storage_config')->load($entity_type_id . '.' . $field_name);
747 748
  }

749 750 751 752 753 754 755 756 757
  /**
   * {@inheritdoc}
   */
  public function isDeletable() {
    // The field storage is not deleted, is configured to be removed when there
    // are no fields and the field storage has no bundles.
    return !$this->deleted && !$this->persist_with_no_fields && count($this->getBundles()) == 0;
  }

758
}