378a95ca94907dc7cbb0206b802d0e9b1fe17274
[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    * @return 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     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
194     $class = $entity_type->getClass();
195     $keys = array_filter($entity_type->getKeys());
196
197     // Fail with an exception for non-fieldable entity types.
198     if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
199       throw new \LogicException("Getting the base fields is not supported for entity type {$entity_type->getLabel()}.");
200     }
201
202     // Retrieve base field definitions.
203     /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $base_field_definitions */
204     $base_field_definitions = $class::baseFieldDefinitions($entity_type);
205
206     // Make sure translatable entity types are correctly defined.
207     if ($entity_type->isTranslatable()) {
208       // The langcode field should always be translatable if the entity type is.
209       if (isset($keys['langcode']) && isset($base_field_definitions[$keys['langcode']])) {
210         $base_field_definitions[$keys['langcode']]->setTranslatable(TRUE);
211       }
212       // A default_langcode field should always be defined.
213       if (!isset($base_field_definitions[$keys['default_langcode']])) {
214         $base_field_definitions[$keys['default_langcode']] = BaseFieldDefinition::create('boolean')
215           ->setLabel($this->t('Default translation'))
216           ->setDescription($this->t('A flag indicating whether this is the default translation.'))
217           ->setTranslatable(TRUE)
218           ->setRevisionable(TRUE)
219           ->setDefaultValue(TRUE);
220       }
221     }
222
223     // Assign base field definitions the entity type provider.
224     $provider = $entity_type->getProvider();
225     foreach ($base_field_definitions as $definition) {
226       // @todo Remove this check once FieldDefinitionInterface exposes a proper
227       //  provider setter. See https://www.drupal.org/node/2225961.
228       if ($definition instanceof BaseFieldDefinition) {
229         $definition->setProvider($provider);
230       }
231     }
232
233     // Retrieve base field definitions from modules.
234     foreach ($this->moduleHandler->getImplementations('entity_base_field_info') as $module) {
235       $module_definitions = $this->moduleHandler->invoke($module, 'entity_base_field_info', [$entity_type]);
236       if (!empty($module_definitions)) {
237         // Ensure the provider key actually matches the name of the provider
238         // defining the field.
239         foreach ($module_definitions as $field_name => $definition) {
240           // @todo Remove this check once FieldDefinitionInterface exposes a
241           //  proper provider setter. See https://www.drupal.org/node/2225961.
242           if ($definition instanceof BaseFieldDefinition && $definition->getProvider() == NULL) {
243             $definition->setProvider($module);
244           }
245           $base_field_definitions[$field_name] = $definition;
246         }
247       }
248     }
249
250     // Automatically set the field name, target entity type and bundle
251     // for non-configurable fields.
252     foreach ($base_field_definitions as $field_name => $base_field_definition) {
253       if ($base_field_definition instanceof BaseFieldDefinition) {
254         $base_field_definition->setName($field_name);
255         $base_field_definition->setTargetEntityTypeId($entity_type_id);
256         $base_field_definition->setTargetBundle(NULL);
257       }
258     }
259
260     // Invoke alter hook.
261     $this->moduleHandler->alter('entity_base_field_info', $base_field_definitions, $entity_type);
262
263     // Ensure defined entity keys are there and have proper revisionable and
264     // translatable values.
265     foreach (array_intersect_key($keys, array_flip(['id', 'revision', 'uuid', 'bundle'])) as $key => $field_name) {
266       if (!isset($base_field_definitions[$field_name])) {
267         throw new \LogicException("The $field_name field definition does not exist and it is used as $key entity key.");
268       }
269       if ($base_field_definitions[$field_name]->isRevisionable()) {
270         throw new \LogicException("The {$base_field_definitions[$field_name]->getLabel()} field cannot be revisionable as it is used as $key entity key.");
271       }
272       if ($base_field_definitions[$field_name]->isTranslatable()) {
273         throw new \LogicException("The {$base_field_definitions[$field_name]->getLabel()} field cannot be translatable as it is used as $key entity key.");
274       }
275     }
276
277     // Make sure translatable entity types define the "langcode" field properly.
278     if ($entity_type->isTranslatable() && (!isset($keys['langcode']) || !isset($base_field_definitions[$keys['langcode']]) || !$base_field_definitions[$keys['langcode']]->isTranslatable())) {
279       throw new \LogicException("The {$entity_type->getLabel()} entity type cannot be translatable as it does not define a translatable \"langcode\" field.");
280     }
281
282     return $base_field_definitions;
283   }
284
285   /**
286    * {@inheritdoc}
287    */
288   public function getFieldDefinitions($entity_type_id, $bundle) {
289     if (!isset($this->fieldDefinitions[$entity_type_id][$bundle])) {
290       $base_field_definitions = $this->getBaseFieldDefinitions($entity_type_id);
291       // Not prepared, try to load from cache.
292       $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId();
293       if ($cache = $this->cacheGet($cid)) {
294         $bundle_field_definitions = $cache->data;
295       }
296       else {
297         // Rebuild the definitions and put it into the cache.
298         $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions);
299         $this->cacheSet($cid, $bundle_field_definitions, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
300       }
301       // Field definitions consist of the bundle specific overrides and the
302       // base fields, merge them together. Use array_replace() to replace base
303       // fields with by bundle overrides and keep them in order, append
304       // additional by bundle fields.
305       $this->fieldDefinitions[$entity_type_id][$bundle] = array_replace($base_field_definitions, $bundle_field_definitions);
306     }
307     return $this->fieldDefinitions[$entity_type_id][$bundle];
308   }
309
310   /**
311    * Builds field definitions for a specific bundle within an entity type.
312    *
313    * @param string $entity_type_id
314    *   The entity type ID. Only entity types that implement
315    *   \Drupal\Core\Entity\FieldableEntityInterface are supported.
316    * @param string $bundle
317    *   The bundle.
318    * @param \Drupal\Core\Field\FieldDefinitionInterface[] $base_field_definitions
319    *   The list of base field definitions.
320    *
321    * @return \Drupal\Core\Field\FieldDefinitionInterface[]
322    *   An array of bundle field definitions, keyed by field name. Does
323    *   not include base fields.
324    */
325   protected function buildBundleFieldDefinitions($entity_type_id, $bundle, array $base_field_definitions) {
326     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
327     $class = $entity_type->getClass();
328
329     // Allow the entity class to provide bundle fields and bundle-specific
330     // overrides of base fields.
331     $bundle_field_definitions = $class::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions);
332
333     // Load base field overrides from configuration. These take precedence over
334     // base field overrides returned above.
335     $base_field_override_ids = array_map(function($field_name) use ($entity_type_id, $bundle) {
336       return $entity_type_id . '.' . $bundle . '.' . $field_name;
337     }, array_keys($base_field_definitions));
338     $base_field_overrides = $this->entityTypeManager->getStorage('base_field_override')->loadMultiple($base_field_override_ids);
339     foreach ($base_field_overrides as $base_field_override) {
340       /** @var \Drupal\Core\Field\Entity\BaseFieldOverride $base_field_override */
341       $field_name = $base_field_override->getName();
342       $bundle_field_definitions[$field_name] = $base_field_override;
343     }
344
345     $provider = $entity_type->getProvider();
346     foreach ($bundle_field_definitions as $definition) {
347       // @todo Remove this check once FieldDefinitionInterface exposes a proper
348       //  provider setter. See https://www.drupal.org/node/2225961.
349       if ($definition instanceof BaseFieldDefinition) {
350         $definition->setProvider($provider);
351       }
352     }
353
354     // Retrieve base field definitions from modules.
355     foreach ($this->moduleHandler->getImplementations('entity_bundle_field_info') as $module) {
356       $module_definitions = $this->moduleHandler->invoke($module, 'entity_bundle_field_info', [$entity_type, $bundle, $base_field_definitions]);
357       if (!empty($module_definitions)) {
358         // Ensure the provider key actually matches the name of the provider
359         // defining the field.
360         foreach ($module_definitions as $field_name => $definition) {
361           // @todo Remove this check once FieldDefinitionInterface exposes a
362           //  proper provider setter. See https://www.drupal.org/node/2225961.
363           if ($definition instanceof BaseFieldDefinition) {
364             $definition->setProvider($module);
365           }
366           $bundle_field_definitions[$field_name] = $definition;
367         }
368       }
369     }
370
371     // Automatically set the field name, target entity type and bundle
372     // for non-configurable fields.
373     foreach ($bundle_field_definitions as $field_name => $field_definition) {
374       if ($field_definition instanceof BaseFieldDefinition) {
375         $field_definition->setName($field_name);
376         $field_definition->setTargetEntityTypeId($entity_type_id);
377         $field_definition->setTargetBundle($bundle);
378       }
379     }
380
381     // Invoke 'per bundle' alter hook.
382     $this->moduleHandler->alter('entity_bundle_field_info', $bundle_field_definitions, $entity_type, $bundle);
383
384     return $bundle_field_definitions;
385   }
386
387   /**
388    * {@inheritdoc}
389    */
390   public function getFieldStorageDefinitions($entity_type_id) {
391     if (!isset($this->fieldStorageDefinitions[$entity_type_id])) {
392       $this->fieldStorageDefinitions[$entity_type_id] = [];
393       // Add all non-computed base fields.
394       foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $definition) {
395         if (!$definition->isComputed()) {
396           $this->fieldStorageDefinitions[$entity_type_id][$field_name] = $definition;
397         }
398       }
399       // Not prepared, try to load from cache.
400       $cid = 'entity_field_storage_definitions:' . $entity_type_id . ':' . $this->languageManager->getCurrentLanguage()->getId();
401       if ($cache = $this->cacheGet($cid)) {
402         $field_storage_definitions = $cache->data;
403       }
404       else {
405         // Rebuild the definitions and put it into the cache.
406         $field_storage_definitions = $this->buildFieldStorageDefinitions($entity_type_id);
407         $this->cacheSet($cid, $field_storage_definitions, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
408       }
409       $this->fieldStorageDefinitions[$entity_type_id] += $field_storage_definitions;
410     }
411     return $this->fieldStorageDefinitions[$entity_type_id];
412   }
413
414   /**
415    * {@inheritdoc}
416    */
417   public function setFieldMap(array $field_map) {
418     $this->fieldMap = $field_map;
419     return $this;
420   }
421
422   /**
423    * {@inheritdoc}
424    */
425   public function getFieldMap() {
426     if (!$this->fieldMap) {
427       // Not prepared, try to load from cache.
428       $cid = 'entity_field_map';
429       if ($cache = $this->cacheGet($cid)) {
430         $this->fieldMap = $cache->data;
431       }
432       else {
433         // The field map is built in two steps. First, add all base fields, by
434         // looping over all fieldable entity types. They always exist for all
435         // bundles, and we do not expect to have so many different entity
436         // types for this to become a bottleneck.
437         foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
438           if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
439             $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
440             foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $base_field_definition) {
441               $this->fieldMap[$entity_type_id][$field_name] = [
442                 'type' => $base_field_definition->getType(),
443                 'bundles' => array_combine($bundles, $bundles),
444               ];
445             }
446           }
447         }
448
449         // In the second step, the per-bundle fields are added, based on the
450         // persistent bundle field map stored in a key value collection. This
451         // data is managed in the EntityManager::onFieldDefinitionCreate()
452         // and EntityManager::onFieldDefinitionDelete() methods. Rebuilding this
453         // information in the same way as base fields would not scale, as the
454         // time to query would grow exponentially with more fields and bundles.
455         // A cache would be deleted during cache clears, which is the only time
456         // it is needed, so a key value collection is used.
457         $bundle_field_maps = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->getAll();
458         foreach ($bundle_field_maps as $entity_type_id => $bundle_field_map) {
459           foreach ($bundle_field_map as $field_name => $map_entry) {
460             if (!isset($this->fieldMap[$entity_type_id][$field_name])) {
461               $this->fieldMap[$entity_type_id][$field_name] = $map_entry;
462             }
463             else {
464               $this->fieldMap[$entity_type_id][$field_name]['bundles'] += $map_entry['bundles'];
465             }
466           }
467         }
468
469         $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, ['entity_types']);
470       }
471     }
472     return $this->fieldMap;
473   }
474
475   /**
476    * {@inheritdoc}
477    */
478   public function getFieldMapByFieldType($field_type) {
479     if (!isset($this->fieldMapByFieldType[$field_type])) {
480       $filtered_map = [];
481       $map = $this->getFieldMap();
482       foreach ($map as $entity_type => $fields) {
483         foreach ($fields as $field_name => $field_info) {
484           if ($field_info['type'] == $field_type) {
485             $filtered_map[$entity_type][$field_name] = $field_info;
486           }
487         }
488       }
489       $this->fieldMapByFieldType[$field_type] = $filtered_map;
490     }
491     return $this->fieldMapByFieldType[$field_type];
492   }
493
494   /**
495    * Builds field storage definitions for an entity type.
496    *
497    * @param string $entity_type_id
498    *   The entity type ID. Only entity types that implement
499    *   \Drupal\Core\Entity\FieldableEntityInterface are supported
500    *
501    * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
502    *   An array of field storage definitions, keyed by field name.
503    */
504   protected function buildFieldStorageDefinitions($entity_type_id) {
505     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
506     $field_definitions = [];
507
508     // Retrieve base field definitions from modules.
509     foreach ($this->moduleHandler->getImplementations('entity_field_storage_info') as $module) {
510       $module_definitions = $this->moduleHandler->invoke($module, 'entity_field_storage_info', [$entity_type]);
511       if (!empty($module_definitions)) {
512         // Ensure the provider key actually matches the name of the provider
513         // defining the field.
514         foreach ($module_definitions as $field_name => $definition) {
515           // @todo Remove this check once FieldDefinitionInterface exposes a
516           //  proper provider setter. See https://www.drupal.org/node/2225961.
517           if ($definition instanceof BaseFieldDefinition) {
518             $definition->setProvider($module);
519           }
520           $field_definitions[$field_name] = $definition;
521         }
522       }
523     }
524
525     // Invoke alter hook.
526     $this->moduleHandler->alter('entity_field_storage_info', $field_definitions, $entity_type);
527
528     return $field_definitions;
529   }
530
531   /**
532    * {@inheritdoc}
533    */
534   public function clearCachedFieldDefinitions() {
535     $this->baseFieldDefinitions = [];
536     $this->fieldDefinitions = [];
537     $this->fieldStorageDefinitions = [];
538     $this->fieldMap = [];
539     $this->fieldMapByFieldType = [];
540     $this->entityDisplayRepository->clearDisplayModeInfo();
541     $this->extraFields = [];
542     Cache::invalidateTags(['entity_field_info']);
543     // The typed data manager statically caches prototype objects with injected
544     // definitions, clear those as well.
545     $this->typedDataManager->clearCachedDefinitions();
546   }
547
548   /**
549    * {@inheritdoc}
550    */
551   public function useCaches($use_caches = FALSE) {
552     $this->useCaches = $use_caches;
553     if (!$use_caches) {
554       $this->fieldDefinitions = [];
555       $this->baseFieldDefinitions = [];
556       $this->fieldStorageDefinitions = [];
557     }
558   }
559
560   /**
561    * {@inheritdoc}
562    */
563   public function getExtraFields($entity_type_id, $bundle) {
564     // Read from the "static" cache.
565     if (isset($this->extraFields[$entity_type_id][$bundle])) {
566       return $this->extraFields[$entity_type_id][$bundle];
567     }
568
569     // Read from the persistent cache. Since hook_entity_extra_field_info() and
570     // hook_entity_extra_field_info_alter() might contain t() calls, we cache
571     // per language.
572     $cache_id = 'entity_bundle_extra_fields:' . $entity_type_id . ':' . $bundle . ':' . $this->languageManager->getCurrentLanguage()->getId();
573     $cached = $this->cacheGet($cache_id);
574     if ($cached) {
575       $this->extraFields[$entity_type_id][$bundle] = $cached->data;
576       return $this->extraFields[$entity_type_id][$bundle];
577     }
578
579     $extra = $this->moduleHandler->invokeAll('entity_extra_field_info');
580     $this->moduleHandler->alter('entity_extra_field_info', $extra);
581     $info = isset($extra[$entity_type_id][$bundle]) ? $extra[$entity_type_id][$bundle] : [];
582     $info += [
583       'form' => [],
584       'display' => [],
585     ];
586
587     // Store in the 'static' and persistent caches.
588     $this->extraFields[$entity_type_id][$bundle] = $info;
589     $this->cacheSet($cache_id, $info, Cache::PERMANENT, [
590       'entity_field_info',
591     ]);
592
593     return $this->extraFields[$entity_type_id][$bundle];
594   }
595
596 }