Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / EntityFieldManager.php
1 <?php
2
3 namespace Drupal\Core\Entity;
4
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Cache\CacheBackendInterface;
7 use Drupal\Core\Cache\UseCacheBackendTrait;
8 use Drupal\Core\Extension\ModuleHandlerInterface;
9 use Drupal\Core\Field\BaseFieldDefinition;
10 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
11 use Drupal\Core\Language\LanguageManagerInterface;
12 use Drupal\Core\StringTranslation\StringTranslationTrait;
13 use Drupal\Core\TypedData\TypedDataManagerInterface;
14
15 /**
16  * Manages the discovery of entity fields.
17  *
18  * This includes field definitions, base field definitions, and field storage
19  * definitions.
20  */
21 class EntityFieldManager implements EntityFieldManagerInterface {
22
23   use UseCacheBackendTrait;
24   use StringTranslationTrait;
25
26   /**
27    * Extra fields by bundle.
28    *
29    * @var array
30    */
31   protected $extraFields = [];
32
33   /**
34    * Static cache of base field definitions.
35    *
36    * @var array
37    */
38   protected $baseFieldDefinitions;
39
40   /**
41    * Static cache of field definitions per bundle and entity type.
42    *
43    * @var array
44    */
45   protected $fieldDefinitions;
46
47   /**
48    * Static cache of field storage definitions per entity type.
49    *
50    * Elements of the array:
51    *  - $entity_type_id: \Drupal\Core\Field\BaseFieldDefinition[]
52    *
53    * @var array
54    */
55   protected $fieldStorageDefinitions;
56
57   /**
58    * An array keyed by entity type. Each value is an array whose keys are
59    * field names and whose value is an array with two entries:
60    *   - type: The field type.
61    *   - bundles: The bundles in which the field appears.
62    *
63    * @var array
64    */
65   protected $fieldMap = [];
66
67   /**
68    * An array keyed by field type. Each value is an array whose key are entity
69    * types including arrays in the same form that $fieldMap.
70    *
71    * It helps access the mapping between types and fields by the field type.
72    *
73    * @var array
74    */
75   protected $fieldMapByFieldType = [];
76
77   /**
78    * The typed data manager.
79    *
80    * @var \Drupal\Core\TypedData\TypedDataManagerInterface
81    */
82   protected $typedDataManager;
83
84   /**
85    * The language manager.
86    *
87    * @var \Drupal\Core\Language\LanguageManagerInterface
88    */
89   protected $languageManager;
90
91   /**
92    * The key-value factory.
93    *
94    * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
95    */
96   protected $keyValueFactory;
97
98   /**
99    * The module handler.
100    *
101    * @var \Drupal\Core\Extension\ModuleHandlerInterface
102    */
103   protected $moduleHandler;
104
105   /**
106    * The entity type manager.
107    *
108    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
109    */
110   protected $entityTypeManager;
111
112   /**
113    * The entity type bundle info.
114    *
115    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
116    */
117   protected $entityTypeBundleInfo;
118
119   /**
120    * The entity display repository.
121    *
122    * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
123    */
124   protected $entityDisplayRepository;
125
126   /**
127    * Constructs a new EntityFieldManager.
128    *
129    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
130    *   The entity type manager.
131    * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
132    *   The entity type bundle info.
133    * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
134    *   The entity display repository.
135    * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
136    *   The typed data manager.
137    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
138    *   The language manager.
139    * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
140    *   The key-value factory.
141    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
142    *   The module handler.
143    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
144    *   The cache backend.
145    */
146   public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityDisplayRepositoryInterface $entity_display_repository, TypedDataManagerInterface $typed_data_manager, LanguageManagerInterface $language_manager, KeyValueFactoryInterface $key_value_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend) {
147     $this->entityTypeManager = $entity_type_manager;
148     $this->entityTypeBundleInfo = $entity_type_bundle_info;
149     $this->entityDisplayRepository = $entity_display_repository;
150
151     $this->typedDataManager = $typed_data_manager;
152     $this->languageManager = $language_manager;
153     $this->keyValueFactory = $key_value_factory;
154     $this->moduleHandler = $module_handler;
155     $this->cacheBackend = $cache_backend;
156   }
157
158   /**
159    * {@inheritdoc}
160    */
161   public function getBaseFieldDefinitions($entity_type_id) {
162     // Check the static cache.
163     if (!isset($this->baseFieldDefinitions[$entity_type_id])) {
164       // Not prepared, try to load from cache.
165       $cid = 'entity_base_field_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->getId();
166       if ($cache = $this->cacheGet($cid)) {
167         $this->baseFieldDefinitions[$entity_type_id] = $cache->data;
168       }
169       else {
170         // Rebuild the definitions and put it into the cache.
171         $this->baseFieldDefinitions[$entity_type_id] = $this->buildBaseFieldDefinitions($entity_type_id);
172         $this->cacheSet($cid, $this->baseFieldDefinitions[$entity_type_id], Cache::PERMANENT, ['entity_types', 'entity_field_info']);
173       }
174     }
175     return $this->baseFieldDefinitions[$entity_type_id];
176   }
177
178   /**
179    * Builds base field definitions for an entity type.
180    *
181    * @param string $entity_type_id
182    *   The entity type ID. Only entity types that implement
183    *   \Drupal\Core\Entity\FieldableEntityInterface are supported.
184    *
185    * @return \Drupal\Core\Field\FieldDefinitionInterface[]
186    *   An array of field definitions, keyed by field name.
187    *
188    * @throws \LogicException
189    *   Thrown if a config entity type is given or if one of the entity keys is
190    *   flagged as translatable.
191    */
192   protected function buildBaseFieldDefinitions($entity_type_id) {
193     /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
194     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
195     $class = $entity_type->getClass();
196     /** @var string[] $keys */
197     $keys = array_filter($entity_type->getKeys());
198
199     // Fail with an exception for non-fieldable entity types.
200     if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
201       throw new \LogicException("Getting the base fields is not supported for entity type {$entity_type->getLabel()}.");
202     }
203
204     // Retrieve base field definitions.
205     /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $base_field_definitions */
206     $base_field_definitions = $class::baseFieldDefinitions($entity_type);
207
208     // Make sure translatable entity types are correctly defined.
209     if ($entity_type->isTranslatable()) {
210       // The langcode field should always be translatable if the entity type is.
211       if (isset($keys['langcode']) && isset($base_field_definitions[$keys['langcode']])) {
212         $base_field_definitions[$keys['langcode']]->setTranslatable(TRUE);
213       }
214       // A default_langcode field should always be defined.
215       if (!isset($base_field_definitions[$keys['default_langcode']])) {
216         $base_field_definitions[$keys['default_langcode']] = BaseFieldDefinition::create('boolean')
217           ->setLabel($this->t('Default translation'))
218           ->setDescription($this->t('A flag indicating whether this is the default translation.'))
219           ->setTranslatable(TRUE)
220           ->setRevisionable(TRUE)
221           ->setDefaultValue(TRUE);
222       }
223     }
224
225     // Make sure that revisionable entity types are correctly defined.
226     if ($entity_type->isRevisionable()) {
227       // Disable the BC layer to prevent a recursion, this only needs the
228       // revision_default key that is always set.
229       $field_name = $entity_type->getRevisionMetadataKeys(FALSE)['revision_default'];
230       $base_field_definitions[$field_name] = BaseFieldDefinition::create('boolean')
231         ->setLabel($this->t('Default revision'))
232         ->setDescription($this->t('A flag indicating whether this was a default revision when it was saved.'))
233         ->setStorageRequired(TRUE)
234         ->setInternal(TRUE)
235         ->setTranslatable(FALSE)
236         ->setRevisionable(TRUE);
237     }
238
239     // Make sure that revisionable and translatable entity types are correctly
240     // defined.
241     if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
242       // The 'revision_translation_affected' field should always be defined.
243       // This field has been added unconditionally in Drupal 8.4.0 and it is
244       // overriding any pre-existing definition on purpose so that any
245       // differences are immediately available in the status report.
246       $base_field_definitions[$keys['revision_translation_affected']] = BaseFieldDefinition::create('boolean')
247         ->setLabel($this->t('Revision translation affected'))
248         ->setDescription($this->t('Indicates if the last edit of a translation belongs to current revision.'))
249         ->setReadOnly(TRUE)
250         ->setRevisionable(TRUE)
251         ->setTranslatable(TRUE);
252     }
253
254     // Assign base field definitions the entity type provider.
255     $provider = $entity_type->getProvider();
256     foreach ($base_field_definitions as $definition) {
257       // @todo Remove this check once FieldDefinitionInterface exposes a proper
258       //  provider setter. See https://www.drupal.org/node/2225961.
259       if ($definition instanceof BaseFieldDefinition) {
260         $definition->setProvider($provider);
261       }
262     }
263
264     // Retrieve base field definitions from modules.
265     foreach ($this->moduleHandler->getImplementations('entity_base_field_info') as $module) {
266       $module_definitions = $this->moduleHandler->invoke($module, 'entity_base_field_info', [$entity_type]);
267       if (!empty($module_definitions)) {
268         // Ensure the provider key actually matches the name of the provider
269         // defining the field.
270         foreach ($module_definitions as $field_name => $definition) {
271           // @todo Remove this check once FieldDefinitionInterface exposes a
272           //  proper provider setter. See https://www.drupal.org/node/2225961.
273           if ($definition instanceof BaseFieldDefinition && $definition->getProvider() == NULL) {
274             $definition->setProvider($module);
275           }
276           $base_field_definitions[$field_name] = $definition;
277         }
278       }
279     }
280
281     // Automatically set the field name, target entity type and bundle
282     // for non-configurable fields.
283     foreach ($base_field_definitions as $field_name => $base_field_definition) {
284       if ($base_field_definition instanceof BaseFieldDefinition) {
285         $base_field_definition->setName($field_name);
286         $base_field_definition->setTargetEntityTypeId($entity_type_id);
287         $base_field_definition->setTargetBundle(NULL);
288       }
289     }
290
291     // Invoke alter hook.
292     $this->moduleHandler->alter('entity_base_field_info', $base_field_definitions, $entity_type);
293
294     // Ensure defined entity keys are there and have proper revisionable and
295     // translatable values.
296     foreach (array_intersect_key($keys, array_flip(['id', 'revision', 'uuid', 'bundle'])) as $key => $field_name) {
297       if (!isset($base_field_definitions[$field_name])) {
298         throw new \LogicException("The $field_name field definition does not exist and it is used as $key entity key.");
299       }
300       if ($base_field_definitions[$field_name]->isRevisionable()) {
301         throw new \LogicException("The {$base_field_definitions[$field_name]->getLabel()} field cannot be revisionable as it is used as $key entity key.");
302       }
303       if ($base_field_definitions[$field_name]->isTranslatable()) {
304         throw new \LogicException("The {$base_field_definitions[$field_name]->getLabel()} field cannot be translatable as it is used as $key entity key.");
305       }
306     }
307
308     // Make sure translatable entity types define the "langcode" field properly.
309     if ($entity_type->isTranslatable() && (!isset($keys['langcode']) || !isset($base_field_definitions[$keys['langcode']]) || !$base_field_definitions[$keys['langcode']]->isTranslatable())) {
310       throw new \LogicException("The {$entity_type->getLabel()} entity type cannot be translatable as it does not define a translatable \"langcode\" field.");
311     }
312
313     return $base_field_definitions;
314   }
315
316   /**
317    * {@inheritdoc}
318    */
319   public function getFieldDefinitions($entity_type_id, $bundle) {
320     if (!isset($this->fieldDefinitions[$entity_type_id][$bundle])) {
321       $base_field_definitions = $this->getBaseFieldDefinitions($entity_type_id);
322       // Not prepared, try to load from cache.
323       $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId();
324       if ($cache = $this->cacheGet($cid)) {
325         $bundle_field_definitions = $cache->data;
326       }
327       else {
328         // Rebuild the definitions and put it into the cache.
329         $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions);
330         $this->cacheSet($cid, $bundle_field_definitions, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
331       }
332       // Field definitions consist of the bundle specific overrides and the
333       // base fields, merge them together. Use array_replace() to replace base
334       // fields with by bundle overrides and keep them in order, append
335       // additional by bundle fields.
336       $this->fieldDefinitions[$entity_type_id][$bundle] = array_replace($base_field_definitions, $bundle_field_definitions);
337     }
338     return $this->fieldDefinitions[$entity_type_id][$bundle];
339   }
340
341   /**
342    * Builds field definitions for a specific bundle within an entity type.
343    *
344    * @param string $entity_type_id
345    *   The entity type ID. Only entity types that implement
346    *   \Drupal\Core\Entity\FieldableEntityInterface are supported.
347    * @param string $bundle
348    *   The bundle.
349    * @param \Drupal\Core\Field\FieldDefinitionInterface[] $base_field_definitions
350    *   The list of base field definitions.
351    *
352    * @return \Drupal\Core\Field\FieldDefinitionInterface[]
353    *   An array of bundle field definitions, keyed by field name. Does
354    *   not include base fields.
355    */
356   protected function buildBundleFieldDefinitions($entity_type_id, $bundle, array $base_field_definitions) {
357     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
358     $class = $entity_type->getClass();
359
360     // Allow the entity class to provide bundle fields and bundle-specific
361     // overrides of base fields.
362     $bundle_field_definitions = $class::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
363
364     // Load base field overrides from configuration. These take precedence over
365     // base field overrides returned above.
366     $base_field_override_ids = array_map(function ($field_name) use ($entity_type_id, $bundle) {
367       return $entity_type_id . '.' . $bundle . '.' . $field_name;
368     }, array_keys($base_field_definitions));
369     $base_field_overrides = $this->entityTypeManager->getStorage('base_field_override')->loadMultiple($base_field_override_ids);
370     foreach ($base_field_overrides as $base_field_override) {
371       /** @var \Drupal\Core\Field\Entity\BaseFieldOverride $base_field_override */
372       $field_name = $base_field_override->getName();
373       $bundle_field_definitions[$field_name] = $base_field_override;
374     }
375
376     $provider = $entity_type->getProvider();
377     foreach ($bundle_field_definitions as $definition) {
378       // @todo Remove this check once FieldDefinitionInterface exposes a proper
379       //  provider setter. See https://www.drupal.org/node/2225961.
380       if ($definition instanceof BaseFieldDefinition) {
381         $definition->setProvider($provider);
382       }
383     }
384
385     // Retrieve base field definitions from modules.
386     foreach ($this->moduleHandler->getImplementations('entity_bundle_field_info') as $module) {
387       $module_definitions = $this->moduleHandler->invoke($module, 'entity_bundle_field_info', [$entity_type, $bundle, $base_field_definitions]);
388       if (!empty($module_definitions)) {
389         // Ensure the provider key actually matches the name of the provider
390         // defining the field.
391         foreach ($module_definitions as $field_name => $definition) {
392           // @todo Remove this check once FieldDefinitionInterface exposes a
393           //  proper provider setter. See https://www.drupal.org/node/2225961.
394           if ($definition instanceof BaseFieldDefinition) {
395             $definition->setProvider($module);
396           }
397           $bundle_field_definitions[$field_name] = $definition;
398         }
399       }
400     }
401
402     // Automatically set the field name, target entity type and bundle
403     // for non-configurable fields.
404     foreach ($bundle_field_definitions as $field_name => $field_definition) {
405       if ($field_definition instanceof BaseFieldDefinition) {
406         $field_definition->setName($field_name);
407         $field_definition->setTargetEntityTypeId($entity_type_id);
408         $field_definition->setTargetBundle($bundle);
409       }
410     }
411
412     // Invoke 'per bundle' alter hook.
413     $this->moduleHandler->alter('entity_bundle_field_info', $bundle_field_definitions, $entity_type, $bundle);
414
415     return $bundle_field_definitions;
416   }
417
418   /**
419    * {@inheritdoc}
420    */
421   public function getFieldStorageDefinitions($entity_type_id) {
422     if (!isset($this->fieldStorageDefinitions[$entity_type_id])) {
423       $this->fieldStorageDefinitions[$entity_type_id] = [];
424       // Add all non-computed base fields.
425       foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $definition) {
426         if (!$definition->isComputed()) {
427           $this->fieldStorageDefinitions[$entity_type_id][$field_name] = $definition;
428         }
429       }
430       // Not prepared, try to load from cache.
431       $cid = 'entity_field_storage_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->getId();
432       if ($cache = $this->cacheGet($cid)) {
433         $field_storage_definitions = $cache->data;
434       }
435       else {
436         // Rebuild the definitions and put it into the cache.
437         $field_storage_definitions = $this->buildFieldStorageDefinitions($entity_type_id);
438         $this->cacheSet($cid, $field_storage_definitions, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
439       }
440       $this->fieldStorageDefinitions[$entity_type_id] += $field_storage_definitions;
441     }
442     return $this->fieldStorageDefinitions[$entity_type_id];
443   }
444
445   /**
446    * {@inheritdoc}
447    */
448   public function setFieldMap(array $field_map) {
449     $this->fieldMap = $field_map;
450     return $this;
451   }
452
453   /**
454    * {@inheritdoc}
455    */
456   public function getFieldMap() {
457     if (!$this->fieldMap) {
458       // Not prepared, try to load from cache.
459       $cid = 'entity_field_map';
460       if ($cache = $this->cacheGet($cid)) {
461         $this->fieldMap = $cache->data;
462       }
463       else {
464         // The field map is built in two steps. First, add all base fields, by
465         // looping over all fieldable entity types. They always exist for all
466         // bundles, and we do not expect to have so many different entity
467         // types for this to become a bottleneck.
468         foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
469           if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
470             $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
471             foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $base_field_definition) {
472               $this->fieldMap[$entity_type_id][$field_name] = [
473                 'type' => $base_field_definition->getType(),
474                 'bundles' => array_combine($bundles, $bundles),
475               ];
476             }
477           }
478         }
479
480         // In the second step, the per-bundle fields are added, based on the
481         // persistent bundle field map stored in a key value collection. This
482         // data is managed in the EntityManager::onFieldDefinitionCreate()
483         // and EntityManager::onFieldDefinitionDelete() methods. Rebuilding this
484         // information in the same way as base fields would not scale, as the
485         // time to query would grow exponentially with more fields and bundles.
486         // A cache would be deleted during cache clears, which is the only time
487         // it is needed, so a key value collection is used.
488         $bundle_field_maps = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->getAll();
489         foreach ($bundle_field_maps as $entity_type_id => $bundle_field_map) {
490           foreach ($bundle_field_map as $field_name => $map_entry) {
491             if (!isset($this->fieldMap[$entity_type_id][$field_name])) {
492               $this->fieldMap[$entity_type_id][$field_name] = $map_entry;
493             }
494             else {
495               $this->fieldMap[$entity_type_id][$field_name]['bundles'] += $map_entry['bundles'];
496             }
497           }
498         }
499
500         $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
501       }
502     }
503     return $this->fieldMap;
504   }
505
506   /**
507    * {@inheritdoc}
508    */
509   public function getFieldMapByFieldType($field_type) {
510     if (!isset($this->fieldMapByFieldType[$field_type])) {
511       $filtered_map = [];
512       $map = $this->getFieldMap();
513       foreach ($map as $entity_type => $fields) {
514         foreach ($fields as $field_name => $field_info) {
515           if ($field_info['type'] == $field_type) {
516             $filtered_map[$entity_type][$field_name] = $field_info;
517           }
518         }
519       }
520       $this->fieldMapByFieldType[$field_type] = $filtered_map;
521     }
522     return $this->fieldMapByFieldType[$field_type];
523   }
524
525   /**
526    * Builds field storage definitions for an entity type.
527    *
528    * @param string $entity_type_id
529    *   The entity type ID. Only entity types that implement
530    *   \Drupal\Core\Entity\FieldableEntityInterface are supported
531    *
532    * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
533    *   An array of field storage definitions, keyed by field name.
534    */
535   protected function buildFieldStorageDefinitions($entity_type_id) {
536     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
537     $field_definitions = [];
538
539     // Retrieve base field definitions from modules.
540     foreach ($this->moduleHandler->getImplementations('entity_field_storage_info') as $module) {
541       $module_definitions = $this->moduleHandler->invoke($module, 'entity_field_storage_info', [$entity_type]);
542       if (!empty($module_definitions)) {
543         // Ensure the provider key actually matches the name of the provider
544         // defining the field.
545         foreach ($module_definitions as $field_name => $definition) {
546           // @todo Remove this check once FieldDefinitionInterface exposes a
547           //  proper provider setter. See https://www.drupal.org/node/2225961.
548           if ($definition instanceof BaseFieldDefinition) {
549             $definition->setProvider($module);
550           }
551           $field_definitions[$field_name] = $definition;
552         }
553       }
554     }
555
556     // Invoke alter hook.
557     $this->moduleHandler->alter('entity_field_storage_info', $field_definitions, $entity_type);
558
559     return $field_definitions;
560   }
561
562   /**
563    * {@inheritdoc}
564    */
565   public function clearCachedFieldDefinitions() {
566     $this->baseFieldDefinitions = [];
567     $this->fieldDefinitions = [];
568     $this->fieldStorageDefinitions = [];
569     $this->fieldMap = [];
570     $this->fieldMapByFieldType = [];
571     $this->entityDisplayRepository->clearDisplayModeInfo();
572     $this->extraFields = [];
573     Cache::invalidateTags(['entity_field_info']);
574     // The typed data manager statically caches prototype objects with injected
575     // definitions, clear those as well.
576     $this->typedDataManager->clearCachedDefinitions();
577   }
578
579   /**
580    * {@inheritdoc}
581    */
582   public function useCaches($use_caches = FALSE) {
583     $this->useCaches = $use_caches;
584     if (!$use_caches) {
585       $this->fieldDefinitions = [];
586       $this->baseFieldDefinitions = [];
587       $this->fieldStorageDefinitions = [];
588     }
589   }
590
591   /**
592    * {@inheritdoc}
593    */
594   public function getExtraFields($entity_type_id, $bundle) {
595     // Read from the "static" cache.
596     if (isset($this->extraFields[$entity_type_id][$bundle])) {
597       return $this->extraFields[$entity_type_id][$bundle];
598     }
599
600     // Read from the persistent cache. Since hook_entity_extra_field_info() and
601     // hook_entity_extra_field_info_alter() might contain t() calls, we cache
602     // per language.
603     $cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId();
604     $cached = $this->cacheGet($cache_id);
605     if ($cached) {
606       $this->extraFields[$entity_type_id][$bundle] = $cached->data;
607       return $this->extraFields[$entity_type_id][$bundle];
608     }
609
610     $extra = $this->moduleHandler->invokeAll('entity_extra_field_info');
611     $this->moduleHandler->alter('entity_extra_field_info', $extra);
612     $info = isset($extra[$entity_type_id][$bundle]) ? $extra[$entity_type_id][$bundle] : [];
613     $info += [
614       'form' => [],
615       'display' => [],
616     ];
617
618     // Store in the 'static' and persistent caches.
619     $this->extraFields[$entity_type_id][$bundle] = $info;
620     $this->cacheSet($cache_id, $info, Cache::PERMANENT, [
621       'entity_field_info',
622     ]);
623
624     return $this->extraFields[$entity_type_id][$bundle];
625   }
626
627 }