b607f3b086f75dd7c71ce0c8b991f9b9553881f1
[yaffs-website] / web / core / modules / field / src / Entity / FieldStorageConfig.php
1 <?php
2
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  *   label_collection = @Translation("Field storages"),
21  *   label_singular = @Translation("field storage"),
22  *   label_plural = @Translation("field storages"),
23  *   label_count = @PluralTranslation(
24  *     singular = "@count field storage",
25  *     plural = "@count field storages",
26  *   ),
27  *   handlers = {
28  *     "access" = "Drupal\field\FieldStorageConfigAccessControlHandler",
29  *     "storage" = "Drupal\field\FieldStorageConfigStorage"
30  *   },
31  *   config_prefix = "storage",
32  *   entity_keys = {
33  *     "id" = "id",
34  *     "label" = "id"
35  *   },
36  *   config_export = {
37  *     "id",
38  *     "field_name",
39  *     "entity_type",
40  *     "type",
41  *     "settings",
42  *     "module",
43  *     "locked",
44  *     "cardinality",
45  *     "translatable",
46  *     "indexes",
47  *     "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    * For fields created through Field UI, this includes the 'field_' prefix.
58    */
59   const NAME_MAX_LENGTH = 32;
60
61   /**
62    * The field ID.
63    *
64    * The ID consists of 2 parts: the entity type and the field name.
65    *
66    * Example: node.body, user.field_main_image.
67    *
68    * @var string
69    */
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    * an entity: $entity->{$field_name}. The maximum length is
77    * Field:NAME_MAX_LENGTH.
78    *
79    * Example: body, field_main_image.
80    *
81    * @var string
82    */
83   protected $field_name;
84
85   /**
86    * The name of the entity type the field can be attached to.
87    *
88    * @var string
89    */
90   protected $entity_type;
91
92   /**
93    * The field type.
94    *
95    * Example: text, integer.
96    *
97    * @var string
98    */
99   protected $type;
100
101   /**
102    * The name of the module that provides the field type.
103    *
104    * @var string
105    */
106   protected $module;
107
108   /**
109    * Field-type specific settings.
110    *
111    * An array of key/value pairs, The keys and default values are defined by the
112    * field type.
113    *
114    * @var array
115    */
116   protected $settings = [];
117
118   /**
119    * The field cardinality.
120    *
121    * The maximum number of values the field can hold. Possible values are
122    * positive integers or
123    * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
124    *
125    * @var int
126    */
127   protected $cardinality = 1;
128
129   /**
130    * Flag indicating whether the field is translatable.
131    *
132    * Defaults to TRUE.
133    *
134    * @var bool
135    */
136   protected $translatable = TRUE;
137
138   /**
139    * Flag indicating whether the field is available for editing.
140    *
141    * If TRUE, some actions not available though the UI (but are still possible
142    * through direct API manipulation):
143    * - field settings cannot be changed,
144    * - new fields cannot be created
145    * - existing fields cannot be deleted.
146    * Defaults to FALSE.
147    *
148    * @var bool
149    */
150   protected $locked = FALSE;
151
152   /**
153    * Flag indicating whether the field storage should be deleted when orphaned.
154    *
155    * By default field storages for configurable fields are removed when there
156    * are no remaining fields using them. If multiple modules provide bundles
157    * which need to use the same field storage then setting this to TRUE will
158    * preserve the field storage regardless of what happens to the bundles. The
159    * classic use case for this is node body field storage since Book, Forum, the
160    * Standard profile and bundle (node type) creation through the UI all use
161    * same field storage.
162    *
163    * @var bool
164    */
165   protected $persist_with_no_fields = FALSE;
166
167   /**
168    * A boolean indicating whether or not the field item uses custom storage.
169    *
170    * @var bool
171    */
172   public $custom_storage = FALSE;
173
174   /**
175    * The custom storage indexes for the field data storage.
176    *
177    * This set of indexes is merged with the "default" indexes specified by the
178    * field type in hook_field_schema() to determine the actual set of indexes
179    * that get created.
180    *
181    * The indexes are defined using the same definition format as Schema API
182    * index specifications. Only columns that are part of the field schema, as
183    * defined by the field type in hook_field_schema(), are allowed.
184    *
185    * Some storage backends might not support indexes, and discard that
186    * information.
187    *
188    * @var array
189    */
190   protected $indexes = [];
191
192   /**
193    * Flag indicating whether the field is deleted.
194    *
195    * The delete() method marks the field as "deleted" and removes the
196    * corresponding entry from the config storage, but keeps its definition in
197    * the state storage while field data is purged by a separate
198    * garbage-collection process.
199    *
200    * Deleted fields stay out of the regular entity lifecycle (notably, their
201    * values are not populated in loaded entities, and are not saved back).
202    *
203    * @var bool
204    */
205   protected $deleted = FALSE;
206
207   /**
208    * The field schema.
209    *
210    * @var array
211    */
212   protected $schema;
213
214   /**
215    * An array of field property definitions.
216    *
217    * @var \Drupal\Core\TypedData\DataDefinitionInterface[]
218    *
219    * @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
220    */
221   protected $propertyDefinitions;
222
223   /**
224    * Static flag set to prevent recursion during field deletes.
225    *
226    * @var bool
227    */
228   protected static $inDeletion = FALSE;
229
230   /**
231    * Constructs a FieldStorageConfig object.
232    *
233    * In most cases, Field entities are created via
234    * FieldStorageConfig::create($values)), where $values is the same parameter
235    * as in this constructor.
236    *
237    * @param array $values
238    *   An array of field properties, keyed by property name. Most array
239    *   elements will be used to set the corresponding properties on the class;
240    *   see the class property documentation for details. Some array elements
241    *   have special meanings and a few are required. Special elements are:
242    *   - name: required. As a temporary Backwards Compatibility layer right now,
243    *     a 'field_name' property can be accepted in place of 'id'.
244    *   - entity_type: required.
245    *   - type: required.
246    *
247    * @see entity_create()
248    */
249   public function __construct(array $values, $entity_type = 'field_storage_config') {
250     // Check required properties.
251     if (empty($values['field_name'])) {
252       throw new FieldException('Attempt to create a field storage without a field name.');
253     }
254     if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['field_name'])) {
255       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");
256     }
257     if (empty($values['type'])) {
258       throw new FieldException("Attempt to create a field storage {$values['field_name']} with no type.");
259     }
260     if (empty($values['entity_type'])) {
261       throw new FieldException("Attempt to create a field storage {$values['field_name']} with no entity_type.");
262     }
263
264     parent::__construct($values, $entity_type);
265   }
266
267   /**
268    * {@inheritdoc}
269    */
270   public function id() {
271     return $this->getTargetEntityTypeId() . '.' . $this->getName();
272   }
273
274   /**
275    * Overrides \Drupal\Core\Entity\Entity::preSave().
276    *
277    * @throws \Drupal\Core\Field\FieldException
278    *   If the field definition is invalid.
279    * @throws \Drupal\Core\Entity\EntityStorageException
280    *   In case of failures at the configuration storage level.
281    */
282   public function preSave(EntityStorageInterface $storage) {
283     // Clear the derived data about the field.
284     unset($this->schema);
285
286     // Filter out unknown settings and make sure all settings are present, so
287     // that a complete field definition is passed to the various hooks and
288     // written to config.
289     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
290     $default_settings = $field_type_manager->getDefaultStorageSettings($this->type);
291     $this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
292
293     if ($this->isNew()) {
294       $this->preSaveNew($storage);
295     }
296     else {
297       $this->preSaveUpdated($storage);
298     }
299
300     parent::preSave($storage);
301   }
302
303   /**
304    * Prepares saving a new field definition.
305    *
306    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
307    *   The entity storage.
308    *
309    * @throws \Drupal\Core\Field\FieldException
310    *   If the field definition is invalid.
311    */
312   protected function preSaveNew(EntityStorageInterface $storage) {
313     $entity_manager = \Drupal::entityManager();
314     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
315
316     // Assign the ID.
317     $this->id = $this->id();
318
319     // Field name cannot be longer than FieldStorageConfig::NAME_MAX_LENGTH
320     // characters. We use mb_strlen() because the DB layer assumes that column
321     // widths are given in characters rather than bytes.
322     if (mb_strlen($this->getName()) > static::NAME_MAX_LENGTH) {
323       throw new FieldException('Attempt to create a field storage with an name longer than ' . static::NAME_MAX_LENGTH . ' characters: ' . $this->getName());
324     }
325
326     // Disallow reserved field names.
327     $disallowed_field_names = array_keys($entity_manager->getBaseFieldDefinitions($this->getTargetEntityTypeId()));
328     if (in_array($this->getName(), $disallowed_field_names)) {
329       throw new FieldException("Attempt to create field storage {$this->getName()} which is reserved by entity type {$this->getTargetEntityTypeId()}.");
330     }
331
332     // Check that the field type is known.
333     $field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
334     if (!$field_type) {
335       throw new FieldException("Attempt to create a field storage of unknown type {$this->getType()}.");
336     }
337     $this->module = $field_type['provider'];
338
339     // Notify the entity manager.
340     $entity_manager->onFieldStorageDefinitionCreate($this);
341   }
342
343   /**
344    * {@inheritdoc}
345    */
346   public function calculateDependencies() {
347     parent::calculateDependencies();
348     // Ensure the field is dependent on the providing module.
349     $this->addDependency('module', $this->getTypeProvider());
350     // Ask the field type for any additional storage dependencies.
351     // @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies()
352     $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType(), FALSE);
353     $this->addDependencies($definition['class']::calculateStorageDependencies($this));
354
355     // Ensure the field is dependent on the provider of the entity type.
356     $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
357     $this->addDependency('module', $entity_type->getProvider());
358     return $this;
359   }
360
361   /**
362    * Prepares saving an updated field definition.
363    *
364    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
365    *   The entity storage.
366    */
367   protected function preSaveUpdated(EntityStorageInterface $storage) {
368     $module_handler = \Drupal::moduleHandler();
369     $entity_manager = \Drupal::entityManager();
370
371     // Some updates are always disallowed.
372     if ($this->getType() != $this->original->getType()) {
373       throw new FieldException("Cannot change the field type for an existing field storage.");
374     }
375     if ($this->getTargetEntityTypeId() != $this->original->getTargetEntityTypeId()) {
376       throw new FieldException("Cannot change the entity type for an existing field storage.");
377     }
378
379     // See if any module forbids the update by throwing an exception. This
380     // invokes hook_field_storage_config_update_forbid().
381     $module_handler->invokeAll('field_storage_config_update_forbid', [$this, $this->original]);
382
383     // Notify the entity manager. A listener can reject the definition
384     // update as invalid by raising an exception, which stops execution before
385     // the definition is written to config.
386     $entity_manager->onFieldStorageDefinitionUpdate($this, $this->original);
387   }
388
389   /**
390    * {@inheritdoc}
391    */
392   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
393     if ($update) {
394       // Invalidate the render cache for all affected entities.
395       $entity_manager = \Drupal::entityManager();
396       $entity_type = $this->getTargetEntityTypeId();
397       if ($entity_manager->hasHandler($entity_type, 'view_builder')) {
398         $entity_manager->getViewBuilder($entity_type)->resetCache();
399       }
400     }
401   }
402
403   /**
404    * {@inheritdoc}
405    */
406   public static function preDelete(EntityStorageInterface $storage, array $field_storages) {
407     /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
408     $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
409
410     // Set the static flag so that we don't delete field storages whilst
411     // deleting fields.
412     static::$inDeletion = TRUE;
413
414     // Delete or fix any configuration that is dependent, for example, fields.
415     parent::preDelete($storage, $field_storages);
416
417     // Keep the field storage definitions in the deleted fields repository so we
418     // can use them later during field_purge_batch().
419     /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
420     foreach ($field_storages as $field_storage) {
421       // Only mark a field for purging if there is data. Otherwise, just remove
422       // it.
423       $target_entity_storage = \Drupal::entityTypeManager()->getStorage($field_storage->getTargetEntityTypeId());
424       if (!$field_storage->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field_storage, TRUE)) {
425         $storage_definition = clone $field_storage;
426         $storage_definition->deleted = TRUE;
427         $deleted_fields_repository->addFieldStorageDefinition($storage_definition);
428       }
429     }
430   }
431
432   /**
433    * {@inheritdoc}
434    */
435   public static function postDelete(EntityStorageInterface $storage, array $fields) {
436     // Notify the storage.
437     foreach ($fields as $field) {
438       if (!$field->deleted) {
439         \Drupal::entityManager()->onFieldStorageDefinitionDelete($field);
440         $field->deleted = TRUE;
441       }
442     }
443     // Unset static flag.
444     static::$inDeletion = FALSE;
445   }
446
447   /**
448    * {@inheritdoc}
449    */
450   public function getSchema() {
451     if (!isset($this->schema)) {
452       // Get the schema from the field item class.
453       $class = $this->getFieldItemClass();
454       $schema = $class::schema($this);
455       // Fill in default values for optional entries.
456       $schema += [
457         'columns' => [],
458         'unique keys' => [],
459         'indexes' => [],
460         'foreign keys' => [],
461       ];
462
463       // Merge custom indexes with those specified by the field type. Custom
464       // indexes prevail.
465       $schema['indexes'] = $this->indexes + $schema['indexes'];
466
467       $this->schema = $schema;
468     }
469
470     return $this->schema;
471   }
472
473   /**
474    * {@inheritdoc}
475    */
476   public function hasCustomStorage() {
477     return $this->custom_storage;
478   }
479
480   /**
481    * {@inheritdoc}
482    */
483   public function isBaseField() {
484     return FALSE;
485   }
486
487   /**
488    * {@inheritdoc}
489    */
490   public function getColumns() {
491     $schema = $this->getSchema();
492     // A typical use case for the method is to iterate on the columns, while
493     // some other use cases rely on identifying the first column with the key()
494     // function. Since the schema is persisted in the Field object, we take care
495     // of resetting the array pointer so that the former does not interfere with
496     // the latter.
497     reset($schema['columns']);
498     return $schema['columns'];
499   }
500
501   /**
502    * {@inheritdoc}
503    */
504   public function getBundles() {
505     if (!$this->isDeleted()) {
506       $map = \Drupal::entityManager()->getFieldMap();
507       if (isset($map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'])) {
508         return $map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'];
509       }
510     }
511     return [];
512   }
513
514   /**
515    * {@inheritdoc}
516    */
517   public function getName() {
518     return $this->field_name;
519   }
520
521   /**
522    * {@inheritdoc}
523    */
524   public function isDeleted() {
525     return $this->deleted;
526   }
527
528   /**
529    * {@inheritdoc}
530    */
531   public function getTypeProvider() {
532     return $this->module;
533   }
534
535   /**
536    * {@inheritdoc}
537    */
538   public function getType() {
539     return $this->type;
540   }
541
542   /**
543    * {@inheritdoc}
544    */
545   public function getSettings() {
546     // @todo FieldTypePluginManager maintains its own static cache. However, do
547     //   some CPU and memory profiling to see if it's worth statically caching
548     //   $field_type_info, or the default field storage and field settings,
549     //   within $this.
550     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
551
552     $settings = $field_type_manager->getDefaultStorageSettings($this->getType());
553     return $this->settings + $settings;
554   }
555
556   /**
557    * {@inheritdoc}
558    */
559   public function getSetting($setting_name) {
560     // @todo See getSettings() about potentially statically caching this.
561     // We assume here that one call to array_key_exists() is more efficient
562     // than calling getSettings() when all we need is a single setting.
563     if (array_key_exists($setting_name, $this->settings)) {
564       return $this->settings[$setting_name];
565     }
566     $settings = $this->getSettings();
567     if (array_key_exists($setting_name, $settings)) {
568       return $settings[$setting_name];
569     }
570     else {
571       return NULL;
572     }
573   }
574
575   /**
576    * {@inheritdoc}
577    */
578   public function setSetting($setting_name, $value) {
579     $this->settings[$setting_name] = $value;
580     return $this;
581   }
582
583   /**
584    * {@inheritdoc}
585    */
586   public function setSettings(array $settings) {
587     $this->settings = $settings + $this->settings;
588     return $this;
589   }
590
591   /**
592    * {@inheritdoc}
593    */
594   public function isTranslatable() {
595     return $this->translatable;
596   }
597
598   /**
599    * {@inheritdoc}
600    */
601   public function isRevisionable() {
602     // All configurable fields are revisionable.
603     return TRUE;
604   }
605
606   /**
607    * {@inheritdoc}
608    */
609   public function setTranslatable($translatable) {
610     $this->translatable = $translatable;
611     return $this;
612   }
613
614   /**
615    * {@inheritdoc}
616    */
617   public function getProvider() {
618     return 'field';
619   }
620
621   /**
622    * {@inheritdoc}
623    */
624   public function getLabel() {
625     return $this->label();
626   }
627
628   /**
629    * {@inheritdoc}
630    */
631   public function getDescription() {
632     return NULL;
633   }
634
635   /**
636    * {@inheritdoc}
637    */
638   public function getCardinality() {
639     /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
640     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
641     $definition = $field_type_manager->getDefinition($this->getType());
642     $enforced_cardinality = isset($definition['cardinality']) ? $definition['cardinality'] : NULL;
643
644     // Enforced cardinality is a positive integer or -1.
645     if ($enforced_cardinality !== NULL && $enforced_cardinality < 1 && $enforced_cardinality !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
646       throw new FieldException("Invalid enforced cardinality '$enforced_cardinality'. Allowed values: a positive integer or -1.");
647     }
648
649     return $enforced_cardinality ?: $this->cardinality;
650   }
651
652   /**
653    * {@inheritdoc}
654    */
655   public function setCardinality($cardinality) {
656     $this->cardinality = $cardinality;
657     return $this;
658   }
659
660   /**
661    * {@inheritdoc}
662    */
663   public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
664     // If the field item class implements the interface, create an orphaned
665     // runtime item object, so that it can be used as the options provider
666     // without modifying the entity being worked on.
667     if (is_subclass_of($this->getFieldItemClass(), OptionsProviderInterface::class)) {
668       $items = $entity->get($this->getName());
669       return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
670     }
671     // @todo: Allow setting custom options provider, see
672     // https://www.drupal.org/node/2002138.
673   }
674
675   /**
676    * {@inheritdoc}
677    */
678   public function isMultiple() {
679     $cardinality = $this->getCardinality();
680     return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
681   }
682
683   /**
684    * {@inheritdoc}
685    */
686   public function isLocked() {
687     return $this->locked;
688   }
689
690   /**
691    * {@inheritdoc}
692    */
693   public function setLocked($locked) {
694     $this->locked = $locked;
695     return $this;
696   }
697
698   /**
699    * {@inheritdoc}
700    */
701   public function getTargetEntityTypeId() {
702     return $this->entity_type;
703   }
704
705   /**
706    * {@inheritdoc}
707    */
708   public function isQueryable() {
709     return TRUE;
710   }
711
712   /**
713    * Determines whether a field has any data.
714    *
715    * @return bool
716    *   TRUE if the field has data for any entity; FALSE otherwise.
717    */
718   public function hasData() {
719     return \Drupal::entityManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
720   }
721
722   /**
723    * Implements the magic __sleep() method.
724    *
725    * Using the Serialize interface and serialize() / unserialize() methods
726    * breaks entity forms in PHP 5.4.
727    * @todo Investigate in https://www.drupal.org/node/2074253.
728    */
729   public function __sleep() {
730     // Only serialize necessary properties, excluding those that can be
731     // recalculated.
732     $properties = get_object_vars($this);
733     unset($properties['schema'], $properties['propertyDefinitions'], $properties['original']);
734     return array_keys($properties);
735   }
736
737   /**
738    * {@inheritdoc}
739    */
740   public function getConstraints() {
741     return [];
742   }
743
744   /**
745    * {@inheritdoc}
746    */
747   public function getConstraint($constraint_name) {
748     return NULL;
749   }
750
751   /**
752    * {@inheritdoc}
753    */
754   public function getPropertyDefinition($name) {
755     if (!isset($this->propertyDefinitions)) {
756       $this->getPropertyDefinitions();
757     }
758     if (isset($this->propertyDefinitions[$name])) {
759       return $this->propertyDefinitions[$name];
760     }
761   }
762
763   /**
764    * {@inheritdoc}
765    */
766   public function getPropertyDefinitions() {
767     if (!isset($this->propertyDefinitions)) {
768       $class = $this->getFieldItemClass();
769       $this->propertyDefinitions = $class::propertyDefinitions($this);
770     }
771     return $this->propertyDefinitions;
772   }
773
774   /**
775    * {@inheritdoc}
776    */
777   public function getPropertyNames() {
778     return array_keys($this->getPropertyDefinitions());
779   }
780
781   /**
782    * {@inheritdoc}
783    */
784   public function getMainPropertyName() {
785     $class = $this->getFieldItemClass();
786     return $class::mainPropertyName();
787   }
788
789   /**
790    * {@inheritdoc}
791    */
792   public function getUniqueStorageIdentifier() {
793     return $this->uuid();
794   }
795
796   /**
797    * Helper to retrieve the field item class.
798    */
799   protected function getFieldItemClass() {
800     $type_definition = \Drupal::typedDataManager()
801       ->getDefinition('field_item:' . $this->getType());
802     return $type_definition['class'];
803   }
804
805   /**
806    * Loads a field config entity based on the entity type and field name.
807    *
808    * @param string $entity_type_id
809    *   ID of the entity type.
810    * @param string $field_name
811    *   Name of the field.
812    *
813    * @return static
814    *   The field config entity if one exists for the provided field name,
815    *   otherwise NULL.
816    */
817   public static function loadByName($entity_type_id, $field_name) {
818     return \Drupal::entityManager()->getStorage('field_storage_config')->load($entity_type_id . '.' . $field_name);
819   }
820
821   /**
822    * {@inheritdoc}
823    */
824   public function isDeletable() {
825     // The field storage is not deleted, is configured to be removed when there
826     // are no fields, the field storage has no bundles, and field storages are
827     // not in the process of being deleted.
828     return !$this->deleted && !$this->persist_with_no_fields && count($this->getBundles()) == 0 && !static::$inDeletion;
829   }
830
831   /**
832    * {@inheritdoc}
833    */
834   public function getIndexes() {
835     return $this->indexes;
836   }
837
838   /**
839    * {@inheritdoc}
840    */
841   public function setIndexes(array $indexes) {
842     $this->indexes = $indexes;
843     return $this;
844   }
845
846 }