diff --git a/core/lib/Drupal/Core/Entity/EntityEvent.php b/core/lib/Drupal/Core/Entity/EntityEvent.php new file mode 100644 index 0000000..f504666 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityEvent.php @@ -0,0 +1,42 @@ +entity = $entity; + } + + /** + * Returns the entity wrapped by this event. + * + * @return \Drupal\Core\Entity\EntityInterface + * The entity object. + */ + public function getEntity() { + return $this->entity; + } + +} diff --git a/core/lib/Drupal/Core/Entity/EntityEvents.php b/core/lib/Drupal/Core/Entity/EntityEvents.php new file mode 100644 index 0000000..94972df --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityEvents.php @@ -0,0 +1,183 @@ + self::CREATE, + 'presave' => self::PRESAVE, + 'insert' => self::INSERT, + 'update' => self::UPDATE, + 'predelete' => self::PREDELETE, + 'delete' => self::DELETE, + ]; + + /** + * Name of the event fired when creating an entity. + * + * This hook runs after a new entity object has just been instantiated. + * + * @see hook_entity_create() + * + * @Event + * + * @var string + */ + const CREATE = 'entity.create'; + + /** + * Name of the event fired before an entity is created or updated. + * + * You can get the original entity object from $entity->original when it is an + * update of the entity. + * + * @see hook_entity_presave() + * + * @Event + * + * @var string + */ + const PRESAVE = 'entity.presave'; + + /** + * Name of the event fired after creating an entity. + * + * This event fires once the entity has been stored. Note that changes to the + * entity made by subscribers to the event will not be saved. + * + * @see hook_entity_insert() + * + * @Event + * + * @var string + */ + const INSERT = 'entity.insert'; + + /** + * Name of the event fired after updating an existing entity. + * + * This event fires once the entity has been stored. Note that changes to the + * entity made by subscribers to the event will not be saved. Get the original + * entity object from $entity->original. + * + * @see hook_entity_update() + * + * @Event + * + * @var string + */ + const UPDATE = 'entity.update'; + + /** + * Name of the event fired before deleting an entity. + * + * @see hook_entity_predelete() + * + * @Event + * + * @var string + */ + const PREDELETE = 'entity.predelete'; + + /** + * Name of the event fired after deleting an entity. + * + * @see hook_entity_delete() + * + * @Event + * + * @var string + */ + const DELETE = 'entity.delete'; + + /** + * Returns the event name for creation of an entity of a specific type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The event name. + */ + public static function create($entity_type_id) { + return self::CREATE . '.' . $entity_type_id; + } + + /** + * Returns the event name when presaving an entity of a specific type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The event name. + */ + public static function presave($entity_type_id) { + return self::PRESAVE . '.' . $entity_type_id; + } + + /** + * Returns the event name for inserting an entity of a specific type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The event name. + */ + public static function insert($entity_type_id) { + return self::INSERT . '.' . $entity_type_id; + } + + /** + * Returns the event name for updating an entity of a specific type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The event name. + */ + public static function update($entity_type_id) { + return self::UPDATE . '.' . $entity_type_id; + } + + /** + * Returns the event name just before deleting an entity of a specific type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The event name. + */ + public static function predelete($entity_type_id) { + return self::PREDELETE . '.' . $entity_type_id; + } + + /** + * Returns the event name for deletion of an entity of a specific type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return string + * The event name. + */ + public static function delete($entity_type_id) { + return self::DELETE . '.' . $entity_type_id; + } + +} diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index 99698df..cf95247 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -167,6 +167,23 @@ protected function invokeHook($hook, EntityInterface $entity) { $this->moduleHandler()->invokeAll($this->entityTypeId . '_' . $hook, [$entity]); // Invoke the respective entity-level hook. $this->moduleHandler()->invokeAll('entity_' . $hook, [$entity]); + + // Dispatch events for the invoked hook. + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ + $event_dispatcher = \Drupal::service('event_dispatcher'); + $event = new EntityEvent($entity); + + // Dispatch an event for the entity type-specific hook (i.e., + // hook_ENTITY_TYPE_$hook). + if (method_exists(EntityEvents::class, $hook)) { + $event_name = call_user_func([EntityEvents::class, $hook], $this->entityTypeId); + $event_dispatcher->dispatch($event_name, $event); + } + + // Dispatch an event for the entity-level hook (i.e., hook_entity_$hook). + if (isset(EntityEvents::$hookToEventMap[$hook])) { + $event_dispatcher->dispatch(EntityEvents::$hookToEventMap[$hook], $event); + } } /** diff --git a/core/modules/system/tests/modules/entity_test_event/entity_test_event.info.yml b/core/modules/system/tests/modules/entity_test_event/entity_test_event.info.yml new file mode 100644 index 0000000..5089f4b --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_event/entity_test_event.info.yml @@ -0,0 +1,4 @@ +name: entity_test_event +type: module +package: Testing +core: 8.x diff --git a/core/modules/system/tests/modules/entity_test_event/entity_test_event.services.yml b/core/modules/system/tests/modules/entity_test_event/entity_test_event.services.yml new file mode 100644 index 0000000..e20270c --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_event/entity_test_event.services.yml @@ -0,0 +1,5 @@ +services: + entity_test_event.event_subscriber: + class: \Drupal\entity_test_event\EventSubscriber\TestEventSubscriber + tags: + - { name: event_subscriber } diff --git a/core/modules/system/tests/modules/entity_test_event/src/EventSubscriber/TestEventSubscriber.php b/core/modules/system/tests/modules/entity_test_event/src/EventSubscriber/TestEventSubscriber.php new file mode 100644 index 0000000..8566ba0 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test_event/src/EventSubscriber/TestEventSubscriber.php @@ -0,0 +1,44 @@ +getEntity()->name->value === 'hei') { + $event->getEntity()->name->value .= ' ho'; + } + } + + public function onUpdate(EntityEvent $event) { + if ($event->getEntity()->name->value === 'hei') { + $event->getEntity()->name->value .= ' ho'; + } + } + + public function onDelete(EntityEvent $event) { + if ($event->getEntity()->name->value === 'hei ho') { + EntityTest::create([ + 'name' => 'hei_ho' + ])->save(); + $event->getEntity()->name->value .= ' ho'; + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[EntityEvents::INSERT] = 'onInsert'; + $events[EntityEvents::UPDATE] = 'onUpdate'; + $events[EntityEvents::DELETE] = 'onDelete'; + return $events; + } + + +} diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityEventsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityEventsTest.php new file mode 100644 index 0000000..99faba98 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityEventsTest.php @@ -0,0 +1,64 @@ +installEntitySchema('entity_test'); + } + + public function testEventsInsert() { + $entity = EntityTest::create([ + 'name' => 'hei', + ]); + $entity->save(); + $this->assertEquals('hei ho', $entity->name->value); + } + + public function testEventsUpdate() { + $entity = EntityTest::create([ + 'name' => 'meh', + ]); + $entity->save(); + $this->assertEquals('meh', $entity->name->value); + + $entity->name->value = 'hei'; + $entity->save(); + $this->assertEquals('hei ho', $entity->name->value); + } + + public function testEventsDelete() { + $entities = \Drupal::entityTypeManager()->getStorage('entity_test') + ->loadByProperties(['name' => 'hei_ho']); + $this->assertCount(0, $entities); + + $entity = EntityTest::create([ + 'name' => 'hei', + ]); + $entity->save(); + $entity->delete(); + + // Note the delete event creates another entity. + $entities = \Drupal::entityTypeManager()->getStorage('entity_test') + ->loadByProperties(['name' => 'hei_ho']); + $this->assertCount(1, $entities); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php index 4942701..6890147 100644 --- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Language\Language; use Drupal\Tests\UnitTestCase; use Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * @coversDefaultClass \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage @@ -119,12 +120,14 @@ protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') { $this->languageManager->expects($this->any()) ->method('getCurrentLanguage') ->will($this->returnValue($language)); + $event_dispatcher = $this->prophesize(EventDispatcherInterface::class); $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager); $this->entityStorage->setModuleHandler($this->moduleHandler); $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); + $container->set('event_dispatcher', $event_dispatcher->reveal()); $container->set('language_manager', $this->languageManager); $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator); \Drupal::setContainer($container); diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php index b3e339b..e18ae59 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php @@ -15,6 +15,7 @@ use Drupal\Core\Language\Language; use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorage @@ -1000,10 +1001,12 @@ public function testCreate() { $language_manager->expects($this->any()) ->method('getCurrentLanguage') ->will($this->returnValue($language)); + $event_dispatcher = $this->prophesize(EventDispatcherInterface::class); $this->container->set('language_manager', $language_manager); $this->container->set('entity.manager', $this->entityManager); $this->container->set('module_handler', $this->moduleHandler); + $this->container->set('event_dispatcher', $event_dispatcher->reveal()); $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase') ->disableOriginalConstructor()