diff --git a/core/lib/Drupal/Core/Entity/EntityTypeListener.php b/core/lib/Drupal/Core/Entity/EntityTypeListener.php index 4bf05404d9..9ab4fab633 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeListener.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeListener.php @@ -77,6 +77,7 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) { } $this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type)); + $this->clearCachedDefinitions(); } /** @@ -97,6 +98,7 @@ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeI $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type); $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original)); + $this->clearCachedDefinitions(); } /** @@ -119,6 +121,7 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) { $this->entityLastInstalledSchemaRepository->deleteLastInstalledDefinition($entity_type_id); $this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type)); + $this->clearCachedDefinitions(); } /** @@ -141,7 +144,16 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En } $this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original)); + $this->clearCachedDefinitions(); } } + /** + * Clears necessary caches to apply entity/field definition updates. + */ + protected function clearCachedDefinitions() { + $this->entityTypeManager->clearCachedDefinitions(); + $this->entityFieldManager->clearCachedFieldDefinitions(); + } + } diff --git a/core/modules/system/tests/modules/entity_test/entity_test.services.yml b/core/modules/system/tests/modules/entity_test/entity_test.services.yml index 4e202ae8fd..212de69c18 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.services.yml +++ b/core/modules/system/tests/modules/entity_test/entity_test.services.yml @@ -1,7 +1,7 @@ services: entity_test.definition.subscriber: class: Drupal\entity_test\EntityTestDefinitionSubscriber - arguments: ['@state', '@entity.last_installed_schema.repository'] + arguments: ['@state', '@entity.last_installed_schema.repository', '@entity_type.manager', '@entity_field.manager'] tags: - { name: event_subscriber } cache_context.entity_test_view_grants: diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php index 03e768fb3c..588c20111b 100644 --- a/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php +++ b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php @@ -2,11 +2,13 @@ namespace Drupal\entity_test; +use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface; use Drupal\Core\Entity\EntityTypeEvents; use Drupal\Core\Entity\EntityTypeEventSubscriberTrait; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeListenerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldStorageDefinitionEvents; use Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait; use Drupal\Core\Field\FieldStorageDefinitionInterface; @@ -36,6 +38,20 @@ class EntityTestDefinitionSubscriber implements EventSubscriberInterface, Entity */ protected $entityLastInstalledSchemaRepository; + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected $entityFieldManager; + /** * Flag determining whether events should be tracked. * @@ -46,9 +62,11 @@ class EntityTestDefinitionSubscriber implements EventSubscriberInterface, Entity /** * {@inheritdoc} */ - public function __construct(StateInterface $state, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository) { + public function __construct(StateInterface $state, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) { $this->state = $state; $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository; + $this->entityTypeManager = $entity_type_manager; + $this->entityFieldManager = $entity_field_manager; } /** @@ -66,6 +84,14 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) { $this->storeDefinitionUpdate(EntityTypeEvents::CREATE); } $this->storeEvent(EntityTypeEvents::CREATE); + + // Retrieve the live entity type definition in order to warm the static + // cache and then insert the new entity type definition, so we can test that + // the cache doesn't get stale after the event has fired. + if ($entity_type->id() === 'entity_test_rev') { + $this->entityTypeManager->getDefinition($entity_type->id()); + $this->state->set('entity_test_rev.entity_type', $entity_type); + } } /** @@ -78,6 +104,14 @@ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeI } $this->storeEvent(EntityTypeEvents::UPDATE); + + // Retrieve the live entity type definition in order to warm the static + // cache and then insert the new entity type definition, so we can test that + // the cache doesn't get stale after the event has fired. + if ($entity_type->id() === 'entity_test_rev') { + $this->entityTypeManager->getDefinition($entity_type->id()); + $this->state->set('entity_test_rev.entity_type', $entity_type); + } } /** @@ -95,6 +129,14 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) { $this->storeDefinitionUpdate(EntityTypeEvents::DELETE); } $this->storeEvent(EntityTypeEvents::DELETE); + + // Retrieve the live entity type definition in order to warm the static + // cache and then delete the new entity type definition, so we can test that + // the cache doesn't get stale after the event has fired. + if ($entity_type->id() === 'entity_test_rev') { + $this->entityTypeManager->getDefinition($entity_type->id()); + $this->state->set('entity_test_rev.entity_type', ''); + } } /** @@ -105,6 +147,14 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $ $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::CREATE); } $this->storeEvent(FieldStorageDefinitionEvents::CREATE); + + // Retrieve the live field storage definitions in order to warm the static + // cache and then insert the new storage definition, so we can test that the + // cache doesn't get stale after the event has fired. + if ($storage_definition->getTargetEntityTypeId() === 'entity_test_rev') { + $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId()); + $this->state->set('entity_test_rev.additional_base_field_definitions', [$storage_definition->getName() => $storage_definition]); + } } /** @@ -116,6 +166,14 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $ $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::UPDATE); } $this->storeEvent(FieldStorageDefinitionEvents::UPDATE); + + // Retrieve the live field storage definitions in order to warm the static + // cache and then insert the new storage definition, so we can test that the + // cache doesn't get stale after the event has fired. + if ($storage_definition->getTargetEntityTypeId() === 'entity_test_rev') { + $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId()); + $this->state->set('entity_test_rev.additional_base_field_definitions', [$storage_definition->getName() => $storage_definition]); + } } /** @@ -126,6 +184,14 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $ $this->storeDefinitionUpdate(FieldStorageDefinitionEvents::DELETE); } $this->storeEvent(FieldStorageDefinitionEvents::DELETE); + + // Retrieve the live field storage definitions in order to warm the static + // cache and then remove the new storage definition, so we can test that the + // cache doesn't get stale after the event has fired. + if ($storage_definition->getTargetEntityTypeId() === 'entity_test_rev') { + $this->entityFieldManager->getFieldStorageDefinitions($storage_definition->getTargetEntityTypeId()); + $this->state->set('entity_test_rev.additional_base_field_definitions', []); + } } /** diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php index afe5af6681..4c2ca91716 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php @@ -847,6 +847,11 @@ public function testDefinitionEvents() { $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.'); $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::CREATE), 'Last installed field storage definition was created before the event was fired.'); + // Check that the newly added field can be retrieved from the live field + // storage definitions. + $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions('entity_test_rev'); + $this->assertArrayHasKey('field_storage_test', $field_storage_definitions); + $updated_storage_definition = clone $storage_definition; $updated_storage_definition->setLabel(new TranslatableMarkup('Updated field storage test')); $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.'); @@ -854,11 +859,21 @@ public function testDefinitionEvents() { $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.'); $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::UPDATE), 'Last installed field storage definition was updated before the event was fired.'); + // Check that the updated field can be retrieved from the live field storage + // definitions. + $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions('entity_test_rev'); + $this->assertEquals(new TranslatableMarkup('Updated field storage test'), $field_storage_definitions['field_storage_test']->getLabel()); + $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.'); \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($storage_definition); $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.'); $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::DELETE), 'Last installed field storage definition was deleted before the event was fired.'); + // Check that the deleted field can no longer be retrieved from the live + // field storage definitions. + $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions('entity_test_rev'); + $this->assertArrayNotHasKey('field_storage_test', $field_storage_definitions); + // Test entity type events. $entity_type = $this->entityTypeManager->getDefinition('entity_test_rev'); @@ -874,10 +889,19 @@ public function testDefinitionEvents() { $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.'); $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::UPDATE), 'Last installed entity type definition was updated before the event was fired.'); + // Check that the updated definition can be retrieved from the live entity + // type definitions. + $entity_type = $this->entityTypeManager->getDefinition('entity_test_rev'); + $this->assertEquals(new TranslatableMarkup('Updated entity test rev'), $entity_type->getLabel()); + $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.'); \Drupal::service('entity_type.listener')->onEntityTypeDelete($entity_type); $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.'); $this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::DELETE), 'Last installed entity type definition was deleted before the event was fired.'); + + // Check that the deleted entity type can no longer be retrieved from the + // live entity type definitions. + $this->assertNull($this->entityTypeManager->getDefinition('entity_test_rev', FALSE)); } /**