FieldStorageConfig.php 24.3 KB
Newer Older
1 2
<?php

3
namespace Drupal\field\Entity;
4 5

use Drupal\Core\Config\Entity\ConfigEntityBase;
6
use Drupal\Core\Entity\EntityStorageInterface;
7
use Drupal\Core\Entity\FieldableEntityInterface;
8
use Drupal\Core\Entity\FieldableEntityStorageInterface;
9
use Drupal\Core\Field\FieldException;
10
use Drupal\Core\Field\FieldStorageDefinitionInterface;
11
use Drupal\Core\TypedData\OptionsProviderInterface;
12
use Drupal\field\FieldStorageConfigInterface;
13 14

/**
15
 * Defines the Field storage configuration entity.
16
 *
17
 * @ConfigEntityType(
18
 *   id = "field_storage_config",
19
 *   label = @Translation("Field storage"),
20 21 22 23 24 25 26
 *   label_collection = @Translation("Field storages"),
 *   label_singular = @Translation("field storage"),
 *   label_plural = @Translation("field storages"),
 *   label_count = @PluralTranslation(
 *     singular = "@count field storage",
 *     plural = "@count field storages",
 *   ),
27
 *   handlers = {
28
 *     "access" = "Drupal\field\FieldStorageConfigAccessControlHandler",
29
 *     "storage" = "Drupal\field\FieldStorageConfigStorage"
30
 *   },
31
 *   config_prefix = "storage",
32 33
 *   entity_keys = {
 *     "id" = "id",
34
 *     "label" = "id"
35 36 37 38 39 40 41 42 43 44 45 46 47
 *   },
 *   config_export = {
 *     "id",
 *     "field_name",
 *     "entity_type",
 *     "type",
 *     "settings",
 *     "module",
 *     "locked",
 *     "cardinality",
 *     "translatable",
 *     "indexes",
 *     "persist_with_no_fields",
48
 *     "custom_storage",
49 50 51
 *   }
 * )
 */
52
class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigInterface {
53 54

  /**
55
   * The maximum length of the field name, in characters.
56 57 58
   *
   * For fields created through Field UI, this includes the 'field_' prefix.
   */
59
  const NAME_MAX_LENGTH = 32;
60 61

  /**
62 63 64 65 66 67 68 69
   * 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
   */
70
  protected $id;
71 72 73

  /**
   * The field name.
74 75
   *
   * This is the name of the property under which the field values are placed in
76 77
   * an entity: $entity->{$field_name}. The maximum length is
   * Field:NAME_MAX_LENGTH.
78 79 80 81 82
   *
   * Example: body, field_main_image.
   *
   * @var string
   */
83
  protected $field_name;
84

85 86 87 88 89
  /**
   * The name of the entity type the field can be attached to.
   *
   * @var string
   */
90
  protected $entity_type;
91

92 93 94
  /**
   * The field type.
   *
95
   * Example: text, integer.
96 97 98
   *
   * @var string
   */
99
  protected $type;
100 101 102 103 104 105

  /**
   * The name of the module that provides the field type.
   *
   * @var string
   */
106
  protected $module;
107 108 109 110 111

  /**
   * Field-type specific settings.
   *
   * An array of key/value pairs, The keys and default values are defined by the
112
   * field type.
113 114 115
   *
   * @var array
   */
116
  protected $settings = [];
117 118 119 120 121

  /**
   * The field cardinality.
   *
   * The maximum number of values the field can hold. Possible values are
122 123
   * positive integers or
   * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
124
   *
125
   * @var int
126
   */
127
  protected $cardinality = 1;
128 129 130 131

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

  /**
   * 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,
144 145
   * - new fields cannot be created
   * - existing fields cannot be deleted.
146 147 148 149
   * Defaults to FALSE.
   *
   * @var bool
   */
150
  protected $locked = FALSE;
151

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  /**
   * 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;

167 168 169 170 171 172 173
  /**
   * A boolean indicating whether or not the field item uses custom storage.
   *
   * @var bool
   */
  public $custom_storage = FALSE;

174 175 176 177
  /**
   * The custom storage indexes for the field data storage.
   *
   * This set of indexes is merged with the "default" indexes specified by the
178 179 180
   * field type in the class implementing
   * \Drupal\Core\Field\FieldItemInterface::schema() method to determine the
   * actual set of indexes that get created.
181 182 183
   *
   * The indexes are defined using the same definition format as Schema API
   * index specifications. Only columns that are part of the field schema, as
184 185
   * defined by the field type in the class implementing
   * \Drupal\Core\Field\FieldItemInterface::schema() method, are allowed.
186 187 188 189 190 191
   *
   * Some storage backends might not support indexes, and discard that
   * information.
   *
   * @var array
   */
192
  protected $indexes = [];
193 194 195 196 197 198 199 200 201 202 203 204 205 206

  /**
   * 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
   */
207
  protected $deleted = FALSE;
208 209 210 211 212 213 214 215

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

216 217 218 219 220 221 222 223 224
  /**
   * An array of field property definitions.
   *
   * @var \Drupal\Core\TypedData\DataDefinitionInterface[]
   *
   * @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
   */
  protected $propertyDefinitions;

225 226 227 228 229 230 231
  /**
   * Static flag set to prevent recursion during field deletes.
   *
   * @var bool
   */
  protected static $inDeletion = FALSE;

232
  /**
233
   * Constructs a FieldStorageConfig object.
234
   *
235 236 237 238
   * In most cases, Field entities are created via
   * FieldStorageConfig::create($values)), where $values is the same parameter
   * as in this constructor.
   *
239 240 241 242 243
   * @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:
244
   *   - name: required. As a temporary Backwards Compatibility layer right now,
245
   *     a 'field_name' property can be accepted in place of 'id'.
246
   *   - entity_type: required.
247
   *   - type: required.
248 249 250
   * @param string $entity_type
   *   (optional) The entity type on which the field should be created.
   *   Defaults to "field_storage_config".
251
   */
252
  public function __construct(array $values, $entity_type = 'field_storage_config') {
253
    // Check required properties.
254 255
    if (empty($values['field_name'])) {
      throw new FieldException('Attempt to create a field storage without a field name.');
256
    }
257
    if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['field_name'])) {
258
      throw new FieldException("Attempt to create a field storage {$values['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");
259
    }
260
    if (empty($values['type'])) {
261
      throw new FieldException("Attempt to create a field storage {$values['field_name']} with no type.");
262 263
    }
    if (empty($values['entity_type'])) {
264
      throw new FieldException("Attempt to create a field storage {$values['field_name']} with no entity_type.");
265
    }
266 267 268 269

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

270 271 272 273
  /**
   * {@inheritdoc}
   */
  public function id() {
274
    return $this->getTargetEntityTypeId() . '.' . $this->getName();
275 276
  }

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

289 290 291 292 293 294 295
    // 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;

296
    if ($this->isNew()) {
297
      $this->preSaveNew($storage);
298 299
    }
    else {
300
      $this->preSaveUpdated($storage);
301
    }
302 303

    parent::preSave($storage);
304
  }
305

306
  /**
307
   * Prepares saving a new field definition.
308
   *
309 310
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
311
   *
312 313
   * @throws \Drupal\Core\Field\FieldException
   *   If the field definition is invalid.
314
   */
315
  protected function preSaveNew(EntityStorageInterface $storage) {
316
    $entity_field_manager = \Drupal::service('entity_field.manager');
317
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
318

319 320 321
    // Assign the ID.
    $this->id = $this->id();

322 323 324 325
    // Field name cannot be longer than FieldStorageConfig::NAME_MAX_LENGTH
    // characters. We use mb_strlen() because the DB layer assumes that column
    // widths are given in characters rather than bytes.
    if (mb_strlen($this->getName()) > static::NAME_MAX_LENGTH) {
326
      throw new FieldException('Attempt to create a field storage with an name longer than ' . static::NAME_MAX_LENGTH . ' characters: ' . $this->getName());
327
    }
328

329
    // Disallow reserved field names.
330
    $disallowed_field_names = array_keys($entity_field_manager->getBaseFieldDefinitions($this->getTargetEntityTypeId()));
331
    if (in_array($this->getName(), $disallowed_field_names)) {
332
      throw new FieldException("Attempt to create field storage {$this->getName()} which is reserved by entity type {$this->getTargetEntityTypeId()}.");
333 334 335
    }

    // Check that the field type is known.
336
    $field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
337
    if (!$field_type) {
338
      throw new FieldException("Attempt to create a field storage of unknown type {$this->getType()}.");
339
    }
340
    $this->module = $field_type['provider'];
341

342
    // Notify the field storage definition listener.
343
    \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($this);
344
  }
345

346 347 348 349 350 351
  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    parent::calculateDependencies();
    // Ensure the field is dependent on the providing module.
352
    $this->addDependency('module', $this->getTypeProvider());
353 354 355 356 357
    // Ask the field type for any additional storage dependencies.
    // @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies()
    $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType(), FALSE);
    $this->addDependencies($definition['class']::calculateStorageDependencies($this));

358
    // Ensure the field is dependent on the provider of the entity type.
359
    $entity_type = \Drupal::entityTypeManager()->getDefinition($this->entity_type);
360
    $this->addDependency('module', $entity_type->getProvider());
361
    return $this;
362 363
  }

364
  /**
365
   * Prepares saving an updated field definition.
366
   *
367 368
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
369
   */
370
  protected function preSaveUpdated(EntityStorageInterface $storage) {
371
    $module_handler = \Drupal::moduleHandler();
372

373
    // Some updates are always disallowed.
374
    if ($this->getType() != $this->original->getType()) {
375
      throw new FieldException(sprintf('Cannot change the field type for an existing field storage. The field storage %s has the type %s.', $this->id(), $this->original->getType()));
376
    }
377
    if ($this->getTargetEntityTypeId() != $this->original->getTargetEntityTypeId()) {
378
      throw new FieldException(sprintf('Cannot change the entity type for an existing field storage. The field storage %s has the type %s.', $this->id(), $this->original->getTargetEntityTypeId()));
379 380 381
    }

    // See if any module forbids the update by throwing an exception. This
382
    // invokes hook_field_storage_config_update_forbid().
383
    $module_handler->invokeAll('field_storage_config_update_forbid', [$this, $this->original]);
384

385 386 387
    // Notify the field storage definition listener. A listener can reject the
    // definition update as invalid by raising an exception, which stops
    // execution before the definition is written to config.
388
    \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionUpdate($this, $this->original);
389
  }
390

391 392 393
  /**
   * {@inheritdoc}
   */
394
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
395 396
    if ($update) {
      // Invalidate the render cache for all affected entities.
397
      $entity_type_manager = \Drupal::entityTypeManager();
398
      $entity_type = $this->getTargetEntityTypeId();
399 400
      if ($entity_type_manager->hasHandler($entity_type, 'view_builder')) {
        $entity_type_manager->getViewBuilder($entity_type)->resetCache();
401 402
      }
    }
403 404 405 406 407
  }

  /**
   * {@inheritdoc}
   */
408
  public static function preDelete(EntityStorageInterface $storage, array $field_storages) {
409 410
    /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
    $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
411

412 413 414 415 416 417
    // Set the static flag so that we don't delete field storages whilst
    // deleting fields.
    static::$inDeletion = TRUE;

    // Delete or fix any configuration that is dependent, for example, fields.
    parent::preDelete($storage, $field_storages);
418

419 420
    // Keep the field storage definitions in the deleted fields repository so we
    // can use them later during field_purge_batch().
421
    /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
422
    foreach ($field_storages as $field_storage) {
423 424 425 426
      // Only mark a field for purging if there is data. Otherwise, just remove
      // it.
      $target_entity_storage = \Drupal::entityTypeManager()->getStorage($field_storage->getTargetEntityTypeId());
      if (!$field_storage->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field_storage, TRUE)) {
427 428 429
        $storage_definition = clone $field_storage;
        $storage_definition->deleted = TRUE;
        $deleted_fields_repository->addFieldStorageDefinition($storage_definition);
430 431 432
      }
    }
  }
433

434 435 436
  /**
   * {@inheritdoc}
   */
437
  public static function postDelete(EntityStorageInterface $storage, array $fields) {
438 439 440
    // Notify the storage.
    foreach ($fields as $field) {
      if (!$field->deleted) {
441
        \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($field);
442
        $field->deleted = TRUE;
443
      }
444
    }
445 446
    // Unset static flag.
    static::$inDeletion = FALSE;
447 448 449
  }

  /**
450
   * {@inheritdoc}
451 452 453
   */
  public function getSchema() {
    if (!isset($this->schema)) {
454
      // Get the schema from the field item class.
455
      $class = $this->getFieldItemClass();
456 457
      $schema = $class::schema($this);
      // Fill in default values for optional entries.
458 459 460 461 462 463
      $schema += [
        'columns' => [],
        'unique keys' => [],
        'indexes' => [],
        'foreign keys' => [],
      ];
464 465 466 467 468 469 470 471 472 473 474

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

475 476 477 478
  /**
   * {@inheritdoc}
   */
  public function hasCustomStorage() {
479
    return $this->custom_storage;
480 481
  }

482 483 484 485 486 487 488
  /**
   * {@inheritdoc}
   */
  public function isBaseField() {
    return FALSE;
  }

489 490 491 492 493 494 495 496
  /**
   * {@inheritdoc}
   */
  public function getColumns() {
    $schema = $this->getSchema();
    return $schema['columns'];
  }

497
  /**
498
   * {@inheritdoc}
499 500
   */
  public function getBundles() {
501
    if (!$this->isDeleted()) {
502
      $map = \Drupal::service('entity_field.manager')->getFieldMap();
503 504
      if (isset($map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'])) {
        return $map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'];
505 506
      }
    }
507
    return [];
508 509
  }

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

517 518 519 520 521 522 523 524 525 526 527 528 529 530
  /**
   * {@inheritdoc}
   */
  public function isDeleted() {
    return $this->deleted;
  }

  /**
   * {@inheritdoc}
   */
  public function getTypeProvider() {
    return $this->module;
  }

531 532 533
  /**
   * {@inheritdoc}
   */
534
  public function getType() {
535 536 537 538 539 540
    return $this->type;
  }

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

548
    $settings = $field_type_manager->getDefaultStorageSettings($this->getType());
549
    return $this->settings + $settings;
550 551 552 553 554
  }

  /**
   * {@inheritdoc}
   */
555 556
  public function getSetting($setting_name) {
    // @todo See getSettings() about potentially statically caching this.
557 558
    // We assume here that one call to array_key_exists() is more efficient
    // than calling getSettings() when all we need is a single setting.
559
    if (array_key_exists($setting_name, $this->settings)) {
560 561
      return $this->settings[$setting_name];
    }
562 563 564
    $settings = $this->getSettings();
    if (array_key_exists($setting_name, $settings)) {
      return $settings[$setting_name];
565
    }
566 567 568
    else {
      return NULL;
    }
569 570
  }

571 572 573 574 575 576 577 578 579 580 581 582
  /**
   * {@inheritdoc}
   */
  public function setSetting($setting_name, $value) {
    $this->settings[$setting_name] = $value;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setSettings(array $settings) {
583
    $this->settings = $settings + $this->settings;
584 585 586
    return $this;
  }

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

594 595 596 597 598 599 600 601
  /**
   * {@inheritdoc}
   */
  public function isRevisionable() {
    // All configurable fields are revisionable.
    return TRUE;
  }

602
  /**
603
   * {@inheritdoc}
604 605 606 607 608 609
   */
  public function setTranslatable($translatable) {
    $this->translatable = $translatable;
    return $this;
  }

610 611 612 613 614 615 616
  /**
   * {@inheritdoc}
   */
  public function getProvider() {
    return 'field';
  }

617 618 619
  /**
   * {@inheritdoc}
   */
620
  public function getLabel() {
621 622 623 624 625 626
    return $this->label();
  }

  /**
   * {@inheritdoc}
   */
627
  public function getDescription() {
628
    return NULL;
629 630 631 632 633
  }

  /**
   * {@inheritdoc}
   */
634
  public function getCardinality() {
635 636 637
    /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
    $definition = $field_type_manager->getDefinition($this->getType());
638
    $enforced_cardinality = isset($definition['cardinality']) ? (int) $definition['cardinality'] : NULL;
639 640 641

    // Enforced cardinality is a positive integer or -1.
    if ($enforced_cardinality !== NULL && $enforced_cardinality < 1 && $enforced_cardinality !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
642
      throw new FieldException("Invalid enforced cardinality '{$definition['cardinality']}'. Allowed values: a positive integer or -1.");
643 644 645
    }

    return $enforced_cardinality ?: $this->cardinality;
646 647
  }

648 649 650 651 652 653 654 655
  /**
   * {@inheritdoc}
   */
  public function setCardinality($cardinality) {
    $this->cardinality = $cardinality;
    return $this;
  }

656 657 658
  /**
   * {@inheritdoc}
   */
659
  public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
660 661 662
    // If the field item class implements the interface, create an orphaned
    // runtime item object, so that it can be used as the options provider
    // without modifying the entity being worked on.
663
    if (is_subclass_of($this->getFieldItemClass(), OptionsProviderInterface::class)) {
664
      $items = $entity->get($this->getName());
665
      return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
666 667 668 669 670
    }
    // @todo: Allow setting custom options provider, see
    // https://www.drupal.org/node/2002138.
  }

671 672 673
  /**
   * {@inheritdoc}
   */
674 675
  public function isMultiple() {
    $cardinality = $this->getCardinality();
676
    return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
677 678
  }

679 680 681 682 683 684 685
  /**
   * {@inheritdoc}
   */
  public function isLocked() {
    return $this->locked;
  }

686 687 688 689 690 691 692 693
  /**
   * {@inheritdoc}
   */
  public function setLocked($locked) {
    $this->locked = $locked;
    return $this;
  }

694 695
  /**
   * {@inheritdoc}
696 697 698 699 700
   */
  public function getTargetEntityTypeId() {
    return $this->entity_type;
  }

701 702 703
  /**
   * Determines whether a field has any data.
   *
704
   * @return bool
705 706 707
   *   TRUE if the field has data for any entity; FALSE otherwise.
   */
  public function hasData() {
708
    return \Drupal::entityTypeManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
709
  }
710 711 712 713 714 715

  /**
   * Implements the magic __sleep() method.
   *
   * Using the Serialize interface and serialize() / unserialize() methods
   * breaks entity forms in PHP 5.4.
716
   * @todo Investigate in https://www.drupal.org/node/1977206.
717 718
   */
  public function __sleep() {
719 720 721
    // Only serialize necessary properties, excluding those that can be
    // recalculated.
    $properties = get_object_vars($this);
722
    unset($properties['schema'], $properties['propertyDefinitions'], $properties['original']);
723
    return array_keys($properties);
724 725
  }

726 727 728 729
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
730
    return [];
731 732
  }

733 734 735 736 737 738 739
  /**
   * {@inheritdoc}
   */
  public function getConstraint($constraint_name) {
    return NULL;
  }

740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
  /**
   * {@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();
  }

778 779 780 781 782 783 784
  /**
   * {@inheritdoc}
   */
  public function getUniqueStorageIdentifier() {
    return $this->uuid();
  }

785 786 787 788
  /**
   * Helper to retrieve the field item class.
   */
  protected function getFieldItemClass() {
789 790 791
    $type_definition = \Drupal::typedDataManager()
      ->getDefinition('field_item:' . $this->getType());
    return $type_definition['class'];
792 793
  }

794 795 796 797 798 799 800 801
  /**
   * 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.
   *
802
   * @return \Drupal\field\FieldStorageConfigInterface|null
803 804 805 806
   *   The field config entity if one exists for the provided field name,
   *   otherwise NULL.
   */
  public static function loadByName($entity_type_id, $field_name) {
807
    return \Drupal::entityTypeManager()->getStorage('field_storage_config')->load($entity_type_id . '.' . $field_name);
808 809
  }

810 811 812 813 814
  /**
   * {@inheritdoc}
   */
  public function isDeletable() {
    // The field storage is not deleted, is configured to be removed when there
815 816 817
    // are no fields, the field storage has no bundles, and field storages are
    // not in the process of being deleted.
    return !$this->deleted && !$this->persist_with_no_fields && count($this->getBundles()) == 0 && !static::$inDeletion;
818 819
  }

820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
  /**
   * {@inheritdoc}
   */
  public function getIndexes() {
    return $this->indexes;
  }

  /**
   * {@inheritdoc}
   */
  public function setIndexes(array $indexes) {
    $this->indexes = $indexes;
    return $this;
  }

835
}