Issue #2332935 by plach, alexpott, dawehner: Allow code to respond to entity/field schema changes.

8.0.x
Nathaniel Catchpole 2014-10-17 23:10:00 +01:00
parent 22c82583c9
commit 47e0030705
20 changed files with 633 additions and 40 deletions

View File

@ -287,13 +287,13 @@ services:
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
module_handler:
class: Drupal\Core\Extension\ModuleHandler
arguments: ['%container.modules%', '@cache.bootstrap']
arguments: ['%container.modules%', '@kernel', '@cache.bootstrap']
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_indicator']
entity.manager:
class: Drupal\Core\Entity\EntityManager
arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed']
arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@entity.definitions.installed', '@event_dispatcher']
parent: container.trait
tags:
- { name: plugin_manager_cache_clear }

View File

@ -52,7 +52,8 @@ interface DrupalKernelInterface extends HttpKernelInterface {
/**
* Gets the current container.
*
* @return ContainerInterface A ContainerInterface instance
* @return \Symfony\Component\DependencyInjection\ContainerInterface
* A ContainerInterface instance.
*/
public function getContainer();

View File

@ -7,8 +7,8 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

View File

@ -10,25 +10,28 @@ namespace Drupal\Core\Entity;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\String;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionEvent;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Manages entity type plugin definitions.
@ -122,6 +125,13 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
*/
protected $installedDefinitions;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Static cache of bundle information.
*
@ -183,8 +193,10 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
* The typed data manager.
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $installed_definitions
* The keyvalue collection for tracking installed definitions.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueStoreInterface $installed_definitions) {
public function __construct(\Traversable $namespaces, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, TranslationInterface $translation_manager, ClassResolverInterface $class_resolver, TypedDataManager $typed_data_manager, KeyValueStoreInterface $installed_definitions, EventDispatcherInterface $event_dispatcher) {
parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface', 'Drupal\Core\Entity\Annotation\EntityType');
$this->setCacheBackend($cache, 'entity_type', array('entity_types'));
@ -195,6 +207,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$this->classResolver = $class_resolver;
$this->typedDataManager = $typed_data_manager;
$this->installedDefinitions = $installed_definitions;
$this->eventDispatcher = $event_dispatcher;
}
/**
@ -986,6 +999,8 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$storage->onEntityTypeCreate($entity_type);
}
$this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
$this->setLastInstalledDefinition($entity_type);
if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
$this->setLastInstalledFieldStorageDefinitions($entity_type_id, $this->getFieldStorageDefinitions($entity_type_id));
@ -1005,6 +1020,8 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$storage->onEntityTypeUpdate($entity_type, $original);
}
$this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
$this->setLastInstalledDefinition($entity_type);
}
@ -1021,6 +1038,8 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$storage->onEntityTypeDelete($entity_type);
}
$this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type));
$this->deleteLastInstalledDefinition($entity_type_id);
}
@ -1037,6 +1056,8 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$storage->onFieldStorageDefinitionCreate($storage_definition);
}
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::CREATE, new FieldStorageDefinitionEvent($storage_definition));
$this->setLastInstalledFieldStorageDefinition($storage_definition);
$this->clearCachedFieldDefinitions();
}
@ -1054,6 +1075,8 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$storage->onFieldStorageDefinitionUpdate($storage_definition, $original);
}
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $original));
$this->setLastInstalledFieldStorageDefinition($storage_definition);
$this->clearCachedFieldDefinitions();
}
@ -1071,6 +1094,8 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
$storage->onFieldStorageDefinitionDelete($storage_definition);
}
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::DELETE, new FieldStorageDefinitionEvent($storage_definition));
$this->deleteLastInstalledFieldStorageDefinition($storage_definition);
$this->clearCachedFieldDefinitions();
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeEvent.
*/
namespace Drupal\Core\Entity;
use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Defines a base class for all entity type events.
*/
class EntityTypeEvent extends GenericEvent {
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The original entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $original;
/**
* Constructs a new EntityTypeEvent.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The field storage definition.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* (optional) The original entity type. This should be passed only when
* updating the entity type.
*/
public function __construct(EntityTypeInterface $entity_type, EntityTypeInterface $original = NULL) {
$this->entityType = $entity_type;
$this->original = $original;
}
/**
* The entity type the event refers to.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
*/
public function getEntityType() {
return $this->entityType;
}
/**
* The original entity type.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
*/
public function getOriginal() {
return $this->original;
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeEventSubscriberTrait.
*/
namespace Drupal\Core\Entity;
/**
* Helper methods for EntityTypeListenerInterface.
*
* This allows a class implementing EntityTypeListenerInterface to subscribe and
* react to entity type events.
*
* @see \Symfony\Component\EventDispatcher\EventSubscriberInterface
* @see \Drupal\Core\Entity\EntityTypeListenerInterface
*/
trait EntityTypeEventSubscriberTrait {
/**
* Returns the subscribed events.
*
* @return array
* An array of subscribed event names.
*
* @see \Symfony\Component\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()
*/
public static function getEntityTypeEvents() {
$event = array('onEntityTypeEvent', 100);
$events[EntityTypeEvents::CREATE][] = $event;
$events[EntityTypeEvents::UPDATE][] = $event;
$events[EntityTypeEvents::DELETE][] = $event;
return $events;
}
/**
* Listener method for any entity type definition event.
*
* @param \Drupal\Core\Entity\EntityTypeEvent $event
* The field storage definition event object.
* @param string $event_name
* The event name.
*/
public function onEntityTypeEvent(EntityTypeEvent $event, $event_name) {
switch ($event_name) {
case EntityTypeEvents::CREATE:
$this->onEntityTypeCreate($event->getEntityType());
break;
case EntityTypeEvents::UPDATE:
$this->onEntityTypeUpdate($event->getEntityType(), $event->getOriginal());
break;
case EntityTypeEvents::DELETE:
$this->onEntityTypeDelete($event->getEntityType());
break;
}
}
/**
* {@inheritdoc}
*/
abstract public function onEntityTypeCreate(EntityTypeInterface $entity_type);
/**
* {@inheritdoc}
*/
abstract public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original);
/**
* {@inheritdoc}
*/
abstract public function onEntityTypeDelete(EntityTypeInterface $entity_type);
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityTypeEvents.
*/
namespace Drupal\Core\Entity;
/**
* Contains all events thrown while handling entity types.
*/
final class EntityTypeEvents {
/**
* 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';
}

View File

@ -12,7 +12,7 @@ use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
use Drupal\Core\Cache\CacheBackendInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\DrupalKernelInterface;
/**
* Class that manages modules in a Drupal installation.
@ -64,6 +64,13 @@ class ModuleHandler implements ModuleHandlerInterface {
*/
protected $hookInfo;
/**
* The drupal kernel.
*
* @var \Drupal\Core\DrupalKernelInterface
*/
protected $kernel;
/**
* Cache backend for storing module hook implementation information.
*
@ -92,17 +99,20 @@ class ModuleHandler implements ModuleHandlerInterface {
* An associative array whose keys are the names of installed modules and
* whose values are Extension class parameters. This is normally the
* %container.modules% parameter being set up by DrupalKernel.
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The drupal kernel.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend for storing module hook implementation information.
*
* @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(), DrupalKernelInterface $kernel, CacheBackendInterface $cache_backend) {
$this->moduleList = array();
foreach ($module_list as $name => $module) {
$this->moduleList[$name] = new Extension($module['type'], $module['pathname'], $module['filename']);
}
$this->kernel = $kernel;
$this->cacheBackend = $cache_backend;
}
@ -792,14 +802,7 @@ class ModuleHandler implements ModuleHandlerInterface {
drupal_static_reset('system_rebuild_module_data');
// Update the kernel to include it.
// This reboots the kernel to register the module's bundle and its
// services in the service container. The $module_filenames argument is
// taken over as %container.modules% parameter, which is passed to a
// fresh ModuleHandler instance upon first retrieval.
// @todo install_begin_request() creates a container without a kernel.
if ($kernel = \Drupal::service('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
$kernel->updateModules($module_filenames, $module_filenames);
}
$this->updateKernel($module_filenames);
// Refresh the schema to include it.
drupal_get_schema(NULL, TRUE);
@ -822,10 +825,10 @@ class ModuleHandler implements ModuleHandlerInterface {
$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.
// 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.
// @todo Clean this up in https://www.drupal.org/node/2350111.
$entity_manager = \Drupal::entityManager();
foreach ($entity_manager->getDefinitions() as $entity_type) {
if ($entity_type->getProvider() == $module) {
@ -950,6 +953,7 @@ class ModuleHandler implements ModuleHandlerInterface {
// Clean up all entity bundles (including fields) of every entity type
// provided by the module that is being uninstalled.
// @todo Clean this up in https://www.drupal.org/node/2350111.
foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->getProvider() == $module) {
foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) {
@ -968,10 +972,10 @@ class ModuleHandler implements ModuleHandlerInterface {
// 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.
// 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.
// @todo Clean this up in https://www.drupal.org/node/2350111.
foreach ($entity_manager->getDefinitions() as $entity_type) {
if ($entity_type->getProvider() == $module) {
$entity_manager->onEntityTypeDelete($entity_type);
@ -1004,7 +1008,7 @@ class ModuleHandler implements ModuleHandlerInterface {
\Drupal::service('router.builder_indicator')->setRebuildNeeded();
// Update the kernel to exclude the uninstalled modules.
\Drupal::service('kernel')->updateModules($module_filenames, $module_filenames);
$this->updateKernel($module_filenames);
// Update the theme registry to remove the newly uninstalled module.
drupal_theme_rebuild();
@ -1084,4 +1088,23 @@ class ModuleHandler implements ModuleHandlerInterface {
$module_data = system_rebuild_module_data();
return $module_data[$module]->info['name'];
}
/**
* Updates the kernel module list.
*
* @param string $module_filenames
* The list of installed modules.
*/
protected function updateKernel($module_filenames) {
// This reboots the kernel to register the module's bundle and its services
// in the service container. The $module_filenames argument is taken over as
// %container.modules% parameter, which is passed to a fresh ModuleHandler
// instance upon first retrieval.
$this->kernel->updateModules($module_filenames, $module_filenames);
// After rebuilding the container we need to update the injected
// dependencies.
$container = $this->kernel->getContainer();
$this->cacheBackend = $container->get('cache.bootstrap');
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\Field\FieldStorageDefinitionEvent.
*/
namespace Drupal\Core\Field;
use Symfony\Component\EventDispatcher\GenericEvent;
/**
* Defines a base class for all field storage definition events.
*/
class FieldStorageDefinitionEvent extends GenericEvent {
/**
* The field storage definition.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface
*/
protected $fieldStorageDefinition;
/**
* The original field storage definition.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface
*/
protected $original;
/**
* Constructs a new FieldStorageDefinitionEvent.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition
* The field storage definition.
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
* (optional) The original field storage definition. This should be passed
* only when updating the storage definition.
*/
public function __construct(FieldStorageDefinitionInterface $field_storage_definition, FieldStorageDefinitionInterface $original = NULL) {
$this->fieldStorageDefinition = $field_storage_definition;
$this->original = $original;
}
/**
* The field storage definition.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
*/
public function getFieldStorageDefinition() {
return $this->fieldStorageDefinition;
}
/**
* The original field storage definition.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
*/
public function getOriginal() {
return $this->original;
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait.
*/
namespace Drupal\Core\Field;
/**
* Helper methods for FieldStorageDefinitionListenerInterface.
*
* This allows a class implementing FieldStorageDefinitionListenerInterface to
* subscribe and react to field storage definition events.
*
* @see \Symfony\Component\EventDispatcher\EventSubscriberInterface
* @see \Drupal\Core\Field\FieldStorageDefinitionListenerInterface
*/
trait FieldStorageDefinitionEventSubscriberTrait {
/**
* Returns the subscribed events.
*
* @return array
* An array of subscribed event names.
*
* @see \Symfony\Component\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()
*/
public static function getFieldStorageDefinitionEvents() {
$event = array('onFieldStorageDefinitionEvent', 100);
$events[FieldStorageDefinitionEvents::CREATE][] = $event;
$events[FieldStorageDefinitionEvents::UPDATE][] = $event;
$events[FieldStorageDefinitionEvents::DELETE][] = $event;
return $events;
}
/**
* Listener method for any field storage definition event.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionEvent $event
* The field storage definition event object.
* @param string $event_name
* The event name.
*/
public function onFieldStorageDefinitionEvent(FieldStorageDefinitionEvent $event, $event_name) {
switch ($event_name) {
case FieldStorageDefinitionEvents::CREATE:
$this->onFieldStorageDefinitionCreate($event->getFieldStorageDefinition());
break;
case FieldStorageDefinitionEvents::UPDATE:
$this->onFieldStorageDefinitionUpdate($event->getFieldStorageDefinition(), $event->getOriginal());
break;
case FieldStorageDefinitionEvents::DELETE:
$this->onFieldStorageDefinitionDelete($event->getFieldStorageDefinition());
break;
}
}
/**
* {@inheritdoc}
*/
abstract public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition);
/**
* {@inheritdoc}
*/
abstract public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original);
/**
* {@inheritdoc}
*/
abstract public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition);
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Field\FieldStorageDefinitionEvent.
*/
namespace Drupal\Core\Field;
/**
* Contains all events thrown while handling field storage definitions.
*/
final class FieldStorageDefinitionEvents {
/**
* 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';
}

View File

@ -11,6 +11,7 @@ use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Plugin\LazyPluginCollection;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* Provides a default plugin collection for a plugin type.
@ -21,6 +22,7 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
* self::$pluginKey.
*/
class DefaultLazyPluginCollection extends LazyPluginCollection {
use DependencySerializationTrait;
/**
* The manager used to instantiate the plugins.

View File

@ -10,6 +10,7 @@ namespace Drupal\Core\Plugin;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Plugin\LazyPluginCollection;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* Provides a default plugin collection for a plugin type.
@ -21,6 +22,7 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
* in self::initializePlugin().
*/
class DefaultSingleLazyPluginCollection extends LazyPluginCollection {
use DependencySerializationTrait;
/**
* The manager used to instantiate the plugins.

View File

@ -8,8 +8,10 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeEvents;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\entity_test\FieldStorageDefinition;
/**
@ -442,6 +444,39 @@ class EntityDefinitionUpdateTest extends EntityUnitTestBase {
}
}
/**
* Tests entity type and field storage definition events.
*/
public function testDefinitionEvents() {
/** @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(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
$this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
$this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
// Test entity type events.
$entity_type = $this->entityManager->getDefinition('entity_test_rev');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
$this->entityManager->onEntityTypeCreate($entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
$this->entityManager->onEntityTypeUpdate($entity_type, $entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
$this->entityManager->onEntityTypeDelete($entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
}
/**
* Updates the 'entity_test_update' entity type to revisionable.
*/

View File

@ -47,7 +47,7 @@ abstract class PluginTestBase extends KernelTestBase {
// as derivatives and ReflectionFactory.
$this->testPluginManager = new TestPluginManager();
$this->mockBlockManager = new MockBlockManager();
$module_handler = new ModuleHandler(array(), new MemoryBackend('plugin'));
$module_handler = new ModuleHandler(array(), $this->container->get('kernel'), new MemoryBackend('plugin'), $this->container->get('event_dispatcher'));
$this->defaultsTestPluginManager = new DefaultsTestPluginManager($module_handler);
// The expected plugin definitions within each manager. Several tests assert

View File

@ -0,0 +1,6 @@
services:
entity_test.definition.subscriber:
class: Drupal\entity_test\EntityTestDefinitionSubscriber
arguments: ['@state']
tags:
- { name: event_subscriber }

View File

@ -0,0 +1,131 @@
<?php
/**
* @file
* Contains \Drupal\entity_test\EntityTestDefinitionSubscriber.
*/
namespace Drupal\entity_test;
use Drupal\Core\Entity\EntityTypeEvents;
use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeListenerInterface;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
use Drupal\Core\State\StateInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Test entity type and field storage definition event subscriber.
*/
class EntityTestDefinitionSubscriber implements EventSubscriberInterface, EntityTypeListenerInterface, FieldStorageDefinitionListenerInterface {
use EntityTypeEventSubscriberTrait;
use FieldStorageDefinitionEventSubscriberTrait;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Flag determining whether events should be tracked.
*
* @var bool
*/
protected $trackEvents = FALSE;
/**
* {@inheritdoc}
*/
function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return static::getEntityTypeEvents() + static::getFieldStorageDefinitionEvents();
}
/**
* {@inheritdoc}
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
$this->storeEvent(EntityTypeEvents::CREATE);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
$this->storeEvent(EntityTypeEvents::UPDATE);
}
/**
* {@inheritdoc}
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
$this->storeEvent(EntityTypeEvents::DELETE);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
$this->storeEvent(FieldStorageDefinitionEvents::CREATE);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
$this->storeEvent(FieldStorageDefinitionEvents::UPDATE);
}
/**
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
$this->storeEvent(FieldStorageDefinitionEvents::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);
}
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\update;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
@ -17,6 +18,7 @@ use Drupal\Core\Utility\ProjectInfo;
* Default implementation of UpdateManagerInterface.
*/
class UpdateManager implements UpdateManagerInterface {
use DependencySerializationTrait;
use StringTranslationTrait;
/**

View File

@ -100,6 +100,13 @@ class EntityManagerTest extends UnitTestCase {
*/
protected $installedDefinitions;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* {@inheritdoc}
*/
@ -140,6 +147,8 @@ class EntityManagerTest extends UnitTestCase {
->disableOriginalConstructor()
->getMock();
$this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->installedDefinitions = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
}
@ -171,7 +180,7 @@ class EntityManagerTest extends UnitTestCase {
->method('getDefinitions')
->will($this->returnValue($definitions));
$this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cache, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions);
$this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cache, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions, $this->eventDispatcher);
$this->entityManager->setContainer($this->container);
$this->entityManager->setDiscovery($this->discovery);
}

View File

@ -17,6 +17,13 @@ use Drupal\Tests\UnitTestCase;
*/
class ModuleHandlerTest extends UnitTestCase {
/**
* The mocked drupal kernel.
*
* @var \Drupal\Core\DrupalKernelInterface
*/
protected $kernel;
/**
* The mocked cache backend.
*
@ -24,7 +31,6 @@ class ModuleHandlerTest extends UnitTestCase {
*/
protected $cacheBackend;
/**
* The tested module handler.
*
@ -38,6 +44,7 @@ class ModuleHandlerTest extends UnitTestCase {
* @covers ::__construct
*/
protected function setUp() {
$this->kernel = $this->getMock('Drupal\Core\DrupalKernelInterface');
$this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->moduleHandler = new ModuleHandler(array(
'module_handler_test' => array(
@ -45,7 +52,7 @@ class ModuleHandlerTest extends UnitTestCase {
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
)
), $this->cacheBackend);
), $this->kernel, $this->cacheBackend);
}
/**
@ -96,7 +103,7 @@ class ModuleHandlerTest extends UnitTestCase {
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
)
), $this->cacheBackend
), $this->kernel, $this->cacheBackend
))
->setMethods(array('load'))
->getMock();
@ -164,7 +171,7 @@ class ModuleHandlerTest extends UnitTestCase {
public function testSetModuleList() {
$module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
->setConstructorArgs(array(
array(), $this->cacheBackend
array(), $this->kernel, $this->cacheBackend
))
->setMethods(array('resetImplementations'))
->getMock();
@ -192,7 +199,7 @@ class ModuleHandlerTest extends UnitTestCase {
$module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
->setConstructorArgs(array(
array(), $this->cacheBackend
array(), $this->kernel, $this->cacheBackend
))
->setMethods(array('resetImplementations'))
->getMock();
@ -214,7 +221,7 @@ class ModuleHandlerTest extends UnitTestCase {
$module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
->setConstructorArgs(array(
array(), $this->cacheBackend
array(), $this->kernel, $this->cacheBackend
))
->setMethods(array('resetImplementations'))
->getMock();
@ -250,7 +257,7 @@ class ModuleHandlerTest extends UnitTestCase {
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
)
), $this->cacheBackend
), $this->kernel, $this->cacheBackend
))
->setMethods(array('loadInclude'))
->getMock();
@ -331,7 +338,7 @@ class ModuleHandlerTest extends UnitTestCase {
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
)
), $this->cacheBackend
), $this->kernel, $this->cacheBackend
))
->setMethods(array('buildImplementationInfo', 'loadInclude'))
->getMock();
@ -366,7 +373,7 @@ class ModuleHandlerTest extends UnitTestCase {
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
)
), $this->cacheBackend
), $this->kernel, $this->cacheBackend
))
->setMethods(array('buildImplementationInfo'))
->getMock();