EntityFieldManager.php 25 KB

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