diff --git a/core/core.services.yml b/core/core.services.yml index 574085b..e4d163a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -287,7 +287,7 @@ services: arguments: ['@container.namespaces', '@cache.discovery', '@module_handler'] module_handler: class: Drupal\Core\Extension\ModuleHandler - arguments: ['%container.modules%', '@cache.bootstrap'] + arguments: ['%container.modules%', '@cache.bootstrap', '@event_dispatcher'] theme_handler: class: Drupal\Core\Extension\ThemeHandler arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder'] diff --git a/core/lib/Drupal/Core/Entity/EntityTypeEvent.php b/core/lib/Drupal/Core/Entity/EntityTypeEvent.php index 7628763..1d03af1 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeEvent.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeEvent.php @@ -14,25 +14,8 @@ */ class EntityTypeEvent extends GenericEvent { - /** - * Event name for entity type creation. - * - * @var string - */ const CREATE = 'entity_type.definition.create'; - - /** - * Event name for entity type update. - * - * @var string - */ const UPDATE = 'entity_type.definition.update'; - - /** - * Event name for entity type deletion. - * - * @var string - */ const DELETE = 'entity_type.definition.delete'; /** diff --git a/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php b/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php index 2e9dcd3..c9fdfb4 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeEventSubscriberTrait.php @@ -8,10 +8,10 @@ namespace Drupal\Core\Entity; /** - * Helper method for EntityTypeListenerInterface. + * Helper methods for EntityTypeListenerInterface. * * This allows a class implementing EntityTypeListenerInterface to subscribe and - * react to field storage definition events. + * react to entity type events. * * @see \Symfony\Component\EventDispatcher\EventSubscriberInterface * @see \Drupal\Core\Entity\EntityTypeListenerInterface diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index d543871..b049230 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -14,6 +14,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Entity\EntityTypeEvent; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Class that manages modules in a Drupal installation. @@ -87,6 +88,13 @@ class ModuleHandler implements ModuleHandlerInterface { protected $alterFunctions; /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + + /** * Constructs a ModuleHandler object. * * @param array $module_list @@ -95,16 +103,19 @@ class ModuleHandler implements ModuleHandlerInterface { * %container.modules% parameter being set up by DrupalKernel. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend * Cache backend for storing module hook implementation information. + * @param $event_dispatcher + * The event dispatcher service. * * @see \Drupal\Core\DrupalKernel * @see \Drupal\Core\CoreServiceProvider */ - public function __construct(array $module_list = array(), CacheBackendInterface $cache_backend) { + public function __construct(array $module_list = array(), CacheBackendInterface $cache_backend, EventDispatcherInterface $event_dispatcher) { $this->moduleList = array(); foreach ($module_list as $name => $module) { $this->moduleList[$name] = new Extension($module['type'], $module['pathname'], $module['filename']); } $this->cacheBackend = $cache_backend; + $this->eventDispatcher = $event_dispatcher; } /** @@ -822,15 +833,12 @@ public function install(array $module_list, $enable_dependencies = TRUE) { $version = max(max($versions), $version); } - // Notify the entity manager that this module's entity types are new, - // so that it can notify all interested handlers. For example, a - // SQL-based storage handler can use this as an opportunity to create - // the necessary database tables. - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ - $event_dispatcher = \Drupal::service('event_dispatcher'); + // Notify interested components that this module's entity types are new. + // For example, a SQL-based storage handler can use this as an + // opportunity to create the necessary database tables. foreach (\Drupal::entityManager()->getDefinitions() as $entity_type) { if ($entity_type->getProvider() == $module) { - $event_dispatcher->dispatch(EntityTypeEvent::CREATE, new EntityTypeEvent($entity_type)); + $this->eventDispatcher->dispatch(EntityTypeEvent::CREATE, new EntityTypeEvent($entity_type)); } } @@ -970,14 +978,12 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Remove all configuration belonging to the module. \Drupal::service('config.manager')->uninstall('module', $module); - // Notify the entity manager that this module's entity types are being - // deleted, so that it can notify all interested handlers. For example, - // a SQL-based storage handler can use this as an opportunity to drop - // the corresponding database tables. - $event_dispatcher = \Drupal::service('event_dispatcher'); + // Notify interested components that this module's entity types are being + // deleted. For example, a SQL-based storage handler can use this as an + // opportunity to drop the corresponding database tables. foreach (\Drupal::entityManager()->getDefinitions() as $entity_type) { if ($entity_type->getProvider() == $module) { - $event_dispatcher->dispatch(EntityTypeEvent::DELETE, new EntityTypeEvent($entity_type)); + $this->eventDispatcher->dispatch(EntityTypeEvent::DELETE, new EntityTypeEvent($entity_type)); } } diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php index 991396f..33c6b83 100644 --- a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php +++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEvent.php @@ -14,25 +14,8 @@ */ class FieldStorageDefinitionEvent extends GenericEvent { - /** - * Event name for field storage definition creation. - * - * @var string - */ const CREATE = 'field_storage.definition.create'; - - /** - * Event name for field storage definition update. - * - * @var string - */ const UPDATE = 'field_storage.definition.update'; - - /** - * Event name for field storage definition deletion. - * - * @var string - */ const DELETE = 'field_storage.definition.delete'; /** @@ -55,7 +38,8 @@ class FieldStorageDefinitionEvent extends GenericEvent { * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition * The field storage definition. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original - * The original field storage definition. + * (optional) The original field storage definition. This should be passed + * only when updating */ public function __construct(FieldStorageDefinitionInterface $field_storage_definition, FieldStorageDefinitionInterface $original = NULL) { $this->fieldStorageDefinition = $field_storage_definition; diff --git a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php index d6eef57..704b390 100644 --- a/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php +++ b/core/lib/Drupal/Core/Field/FieldStorageDefinitionEventSubscriberTrait.php @@ -8,7 +8,7 @@ namespace Drupal\Core\Field; /** - * Helper method for FieldStorageDefinitionListenerInterface. + * Helper methods for FieldStorageDefinitionListenerInterface. * * This allows a class implementing FieldStorageDefinitionListenerInterface to * subscribe and react to field storage definition events. diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php index 752a1f3..9483d1b 100644 --- a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php @@ -8,8 +8,10 @@ namespace Drupal\system\Tests\Entity; use Drupal\Core\Entity\EntityStorageException; +use Drupal\Core\Entity\EntityTypeEvent; use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Field\FieldStorageDefinitionEvent; use Drupal\entity_test\FieldStorageDefinition; /** @@ -443,6 +445,41 @@ public function testEntityIndexCreateWithData() { } /** + * Tests entity type and field storage definition events. + */ + public function testDefinitionEvents() { + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ + $event_dispatcher = $this->container->get('event_dispatcher'); + /** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */ + $event_subscriber = $this->container->get('entity_test.definition.subscriber'); + $event_subscriber->enableEventTracking(); + + // Test field storage definition events. + $storage_definition = current($this->entityManager->getFieldStorageDefinitions('entity_test_rev')); + $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvent::DELETE), 'Entity type delete was not dispatched yet.'); + $event_dispatcher->dispatch(FieldStorageDefinitionEvent::DELETE, new FieldStorageDefinitionEvent($storage_definition)); + $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvent::DELETE), 'Entity type delete event successfully dispatched.'); + $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvent::CREATE), 'Entity type create was not dispatched yet.'); + $event_dispatcher->dispatch(FieldStorageDefinitionEvent::CREATE, new FieldStorageDefinitionEvent($storage_definition)); + $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvent::CREATE), 'Entity type create event successfully dispatched.'); + $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvent::UPDATE), 'Entity type update was not dispatched yet.'); + $event_dispatcher->dispatch(FieldStorageDefinitionEvent::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $storage_definition)); + $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvent::UPDATE), 'Entity type update event successfully dispatched.'); + + // Test entity type events. + $entity_type = $this->entityManager->getDefinition('entity_test_rev'); + $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvent::CREATE), 'Entity type create was not dispatched yet.'); + $event_dispatcher->dispatch(EntityTypeEvent::CREATE, new EntityTypeEvent($entity_type)); + $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvent::CREATE), 'Entity type create event successfully dispatched.'); + $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvent::UPDATE), 'Entity type update was not dispatched yet.'); + $event_dispatcher->dispatch(EntityTypeEvent::UPDATE, new EntityTypeEvent($entity_type, $entity_type)); + $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvent::UPDATE), 'Entity type update event successfully dispatched.'); + $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvent::DELETE), 'Entity type delete was not dispatched yet.'); + $event_dispatcher->dispatch(EntityTypeEvent::DELETE, new EntityTypeEvent($entity_type)); + $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvent::DELETE), 'Entity type delete event successfully dispatched.'); + } + + /** * Updates the 'entity_test_update' entity type to revisionable. */ protected function updateEntityTypeToRevisionable() { 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 new file mode 100644 index 0000000..8769fbc --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/entity_test.services.yml @@ -0,0 +1,6 @@ +services: + entity_test.definition.subscriber: + class: Drupal\entity_test\EntityTestDefinitionSubscriber + arguments: ['@state'] + tags: + - { name: event_subscriber } diff --git a/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php new file mode 100644 index 0000000..a789d7b --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/EntityTestDefinitionSubscriber.php @@ -0,0 +1,132 @@ +state = $state; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return static::getEntityTypeEvents() + static::getFieldStorageDefinitionEvents(); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeCreate(EntityTypeInterface $entity_type) { + $this->storeEvent(EntityTypeEvent::CREATE); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + $this->storeEvent(EntityTypeEvent::UPDATE); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDelete(EntityTypeInterface $entity_type) { + $this->storeEvent(EntityTypeEvent::DELETE); + } + + /** + * {@inheritdoc} + */ + public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { + $this->storeEvent(FieldStorageDefinitionEvent::CREATE); + } + + /** + * {@inheritdoc} + */ + public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { + $this->storeEvent(FieldStorageDefinitionEvent::UPDATE); + } + + /** + * {@inheritdoc} + */ + public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) { + $this->storeEvent(FieldStorageDefinitionEvent::DELETE); + } + + /** + * Enables event tracking. + */ + public function enableEventTracking() { + $this->trackEvents = TRUE; + } + + /** + * Checks whether an event has been dispatched. + * + * @param string $event_name + * The event name. + * + * @return bool + * TRUE if the event has been dispatched, FALSE otherwise. + */ + public function hasEventFired($event_name) { + return (bool) $this->state->get($event_name); + } + + /** + * Stores the specified event. + * + * @param string $event_name + * The event name. + */ + protected function storeEvent($event_name) { + if ($this->trackEvents) { + $this->state->set($event_name, TRUE); + } + } + +} diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php index bc5da97..f4c38a5 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php @@ -24,6 +24,12 @@ class ModuleHandlerTest extends UnitTestCase { */ protected $cacheBackend; + /** + * The mocked event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; /** * The tested module handler. @@ -39,13 +45,14 @@ class ModuleHandlerTest extends UnitTestCase { */ protected function setUp() { $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->eventDispatcher = $this->getMock('\Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->moduleHandler = new ModuleHandler(array( 'module_handler_test' => array( 'type' => 'module', 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'filename' => 'module_handler_test.module', ) - ), $this->cacheBackend); + ), $this->cacheBackend, $this->eventDispatcher); } /** @@ -96,7 +103,7 @@ public function testModuleReloading() { 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'filename' => 'module_handler_test.module', ) - ), $this->cacheBackend + ), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('load')) ->getMock(); @@ -164,7 +171,7 @@ public function testGetModuleWithNonExistingModule() { public function testSetModuleList() { $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler') ->setConstructorArgs(array( - array(), $this->cacheBackend + array(), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('resetImplementations')) ->getMock(); @@ -192,7 +199,7 @@ public function testAddModule() { $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler') ->setConstructorArgs(array( - array(), $this->cacheBackend + array(), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('resetImplementations')) ->getMock(); @@ -214,7 +221,7 @@ public function testAddProfile() { $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler') ->setConstructorArgs(array( - array(), $this->cacheBackend + array(), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('resetImplementations')) ->getMock(); @@ -250,7 +257,7 @@ public function testLoadAllIncludes() { 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'filename' => 'module_handler_test.module', ) - ), $this->cacheBackend + ), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('loadInclude')) ->getMock(); @@ -331,7 +338,7 @@ public function testCachedGetImplementations() { 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'filename' => 'module_handler_test.module', ) - ), $this->cacheBackend + ), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('buildImplementationInfo', 'loadInclude')) ->getMock(); @@ -366,7 +373,7 @@ public function testCachedGetImplementationsMissingMethod() { 'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'filename' => 'module_handler_test.module', ) - ), $this->cacheBackend + ), $this->cacheBackend, $this->eventDispatcher )) ->setMethods(array('buildImplementationInfo')) ->getMock();