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