diff --git a/core/core.services.yml b/core/core.services.yml index 5959c44..1aa846a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -433,7 +433,7 @@ services: arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder'] 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', '@event_dispatcher'] + arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@keyvalue', '@event_dispatcher'] parent: container.trait tags: - { name: plugin_manager_cache_clear } @@ -442,11 +442,6 @@ services: arguments: ['@entity.manager'] tags: - { name: event_subscriber } - entity.definitions.installed: - class: Drupal\Core\KeyValueStore\KeyValueStoreInterface - factory_method: get - factory_service: keyvalue - arguments: ['entity.definitions.installed'] entity.definition_update_manager: class: Drupal\Core\Entity\EntityDefinitionUpdateManager arguments: ['@entity.manager'] diff --git a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php index 5037628..496bb1b 100644 --- a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldDefinitionListenerInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionListenerInterface; @@ -20,7 +21,7 @@ * * For example, configurable fields defined and exposed by field.module. */ -interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface { +interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface { /** * Determines if the storage contains any data. @@ -31,37 +32,6 @@ public function hasData(); /** - * Reacts to the creation of a field. - * - * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition - * The field definition created. - */ - public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition); - - /** - * Reacts to the update of a field. - * - * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition - * The field definition being updated. - * @param \Drupal\Core\Field\FieldDefinitionInterface $original - * The original field definition; i.e., the definition before the update. - */ - public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original); - - /** - * Reacts to the deletion of a field. - * - * Stored values should not be wiped at once, but marked as 'deleted' so that - * they can go through a proper purge process later on. - * - * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition - * The field definition being deleted. - * - * @see purgeFieldData() - */ - public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition); - - /** * Purges a batch of field data. * * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index fcba978..55a23a0 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -18,10 +18,13 @@ use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionEvent; use Drupal\Core\Field\FieldStorageDefinitionEvents; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionListenerInterface; +use Drupal\Core\KeyValueStore\KeyValueFactory; +use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; use Drupal\Core\KeyValueStore\KeyValueStoreInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -121,11 +124,11 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa protected $languageManager; /** - * The keyvalue collection for tracking installed definitions. + * The keyvalue factory. * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface */ - protected $installedDefinitions; + protected $keyValueFactory; /** * The event dispatcher. @@ -193,12 +196,12 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa * The class resolver. * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager * The typed data manager. - * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $installed_definitions - * The keyvalue collection for tracking installed definitions. + * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory + * The keyvalue factory. * @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, EventDispatcherInterface $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, KeyValueFactoryInterface $key_value_factory, EventDispatcherInterface $event_dispatcher) { parent::__construct('Entity', $namespaces, $module_handler, 'Drupal\Core\Entity\EntityInterface'); $this->setCacheBackend($cache, 'entity_type', array('entity_types')); @@ -209,7 +212,7 @@ public function __construct(\Traversable $namespaces, ModuleHandlerInterface $mo $this->translationManager = $translation_manager; $this->classResolver = $class_resolver; $this->typedDataManager = $typed_data_manager; - $this->installedDefinitions = $installed_definitions; + $this->keyValueFactory = $key_value_factory; $this->eventDispatcher = $event_dispatcher; } @@ -642,19 +645,34 @@ public function getFieldMap() { $this->fieldMap = $cache->data; } else { - // Rebuild the definitions and put it into the cache. + // First, add all base fields, they are not explicitly tracked as they + // are available on all bundles. foreach ($this->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) { - foreach ($this->getBundleInfo($entity_type_id) as $bundle => $bundle_info) { - foreach ($this->getFieldDefinitions($entity_type_id, $bundle) as $field_name => $field_definition) { - $this->fieldMap[$entity_type_id][$field_name]['type'] = $field_definition->getType(); - $this->fieldMap[$entity_type_id][$field_name]['bundles'][] = $bundle; - } + $bundles = array_keys($this->getBundleInfo($entity_type_id)); + $base_fields = $this->getBaseFieldDefinitions($entity_type_id); + foreach ($base_fields as $field_name => $base_field_definition) { + $this->fieldMap[$entity_type_id][$field_name]['type'] = $base_field_definition->getType(); + $this->fieldMap[$entity_type_id][$field_name]['bundles'] = $bundles; } } } - $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types', 'entity_field_info')); + // Add per-bundle field definitions based on the stored field bundle + // map. + $entity_field_bundle_map = $this->keyValueFactory->get('entity.definitions.field_map')->getAll(); + foreach ($entity_field_bundle_map as $entity_type_id => $field_bundle_map) { + foreach ($field_bundle_map as $field_name => $field_bundles_type) { + if (!isset($this->fieldMap[$entity_type_id][$field_name])) { + $this->fieldMap[$entity_type_id][$field_name] = $field_bundles_type; + } + else { + $this->fieldMap[$entity_type_id][$field_name]['bundles'] = array_merge($this->fieldMap[$entity_type_id][$field_name]['bundles'], $field_bundles_type['bundles']); + } + } + } + + $this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types')); } } return $this->fieldMap; @@ -680,6 +698,50 @@ public function getFieldMapByFieldType($field_type) { } /** + * {@inheritdoc} + */ + public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition) { + $this->getStorage($field_definition->getTargetEntityTypeId())->onFieldDefinitionCreate($field_definition); + $field_bundle_map = $this->keyValueFactory->get('entity.definitions.field_map')->get($field_definition->getTargetEntityTypeId()); + if (!isset($field_bundle_map[$field_definition->getName()])) { + $field_bundle_map[$field_definition->getName()] = [ + 'type' => $field_definition->getType(), + 'bundles' => [], + ]; + } + $field_bundle_map[$field_definition->getName()]['bundles'][] = $field_definition->getTargetBundle(); + $this->keyValueFactory->get('entity.definitions.field_map')->set($field_definition->getTargetEntityTypeId(), $field_bundle_map); + $this->cacheBackend->delete('entity_field_map'); + $this->fieldMap = []; + } + + /** + * {@inheritdoc} + */ + public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original) { + $this->getStorage($field_definition->getTargetEntityTypeId())->onFieldDefinitionUpdate($field_definition, $original); + } + + /** + * {@inheritdoc} + */ + public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) { + $this->getStorage($field_definition->getTargetEntityTypeId())->onFieldDefinitionDelete($field_definition); + $field_bundle_map = $this->keyValueFactory->get('entity.definitions.field_map')->get($field_definition->getTargetEntityTypeId()); + $key = array_search($field_definition->getTargetBundle(), $field_bundle_map[$field_definition->getName()]['bundles']); + unset($field_bundle_map[$field_definition->getName()]['bundles'][$key]); + if (empty($field_bundle_map[$field_definition->getName()]['bundles'])) { + unset($field_bundle_map[$field_definition->getName()]); + } + else { + $field_bundle_map[$field_definition->getName()]['bundles'] = array_values($field_bundle_map[$field_definition->getName()]['bundles']); + } + $this->keyValueFactory->get('entity.definitions.field_map')->set($field_definition->getTargetEntityTypeId(), $field_bundle_map); + $this->cacheBackend->delete('entity_field_map'); + $this->fieldMap = []; + } + + /** * Builds field storage definitions for an entity type. * * @param string $entity_type_id @@ -1243,7 +1305,7 @@ public function onBundleDelete($bundle, $entity_type_id) { * {@inheritdoc} */ public function getLastInstalledDefinition($entity_type_id) { - return $this->installedDefinitions->get($entity_type_id . '.entity_type'); + return $this->keyValueFactory->get('entity.definitions.installed')->get($entity_type_id . '.entity_type'); } /** @@ -1267,7 +1329,7 @@ public function useCaches($use_caches = FALSE) { */ protected function setLastInstalledDefinition(EntityTypeInterface $entity_type) { $entity_type_id = $entity_type->id(); - $this->installedDefinitions->set($entity_type_id . '.entity_type', $entity_type); + $this->keyValueFactory->get('entity.definitions.installed')->set($entity_type_id . '.entity_type', $entity_type); } /** @@ -1277,18 +1339,18 @@ protected function setLastInstalledDefinition(EntityTypeInterface $entity_type) * The entity type definition identifier. */ protected function deleteLastInstalledDefinition($entity_type_id) { - $this->installedDefinitions->delete($entity_type_id . '.entity_type'); + $this->keyValueFactory->get('entity.definitions.installed')->delete($entity_type_id . '.entity_type'); // Clean up field storage definitions as well. Even if the entity type // isn't currently fieldable, there might be legacy definitions or an // empty array stored from when it was. - $this->installedDefinitions->delete($entity_type_id . '.field_storage_definitions'); + $this->keyValueFactory->get('entity.definitions.installed')->delete($entity_type_id . '.field_storage_definitions'); } /** * {@inheritdoc} */ public function getLastInstalledFieldStorageDefinitions($entity_type_id) { - return $this->installedDefinitions->get($entity_type_id . '.field_storage_definitions', array()); + return $this->keyValueFactory->get('entity.definitions.installed')->get($entity_type_id . '.field_storage_definitions', array()); } /** @@ -1300,7 +1362,7 @@ public function getLastInstalledFieldStorageDefinitions($entity_type_id) { * An array of field storage definitions. */ protected function setLastInstalledFieldStorageDefinitions($entity_type_id, array $storage_definitions) { - $this->installedDefinitions->set($entity_type_id . '.field_storage_definitions', $storage_definitions); + $this->keyValueFactory->get('entity.definitions.installed')->set($entity_type_id . '.field_storage_definitions', $storage_definitions); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php index b3bda8b..8cc8460 100644 --- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php @@ -9,12 +9,14 @@ use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface; use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldDefinitionListenerInterface; use Drupal\Core\Field\FieldStorageDefinitionListenerInterface; /** * Provides an interface for entity type managers. */ -interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, CachedDiscoveryInterface { +interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface, EntityBundleListenerInterface, FieldStorageDefinitionListenerInterface, FieldDefinitionListenerInterface, CachedDiscoveryInterface { /** * Builds a list of entity type labels suitable for a Form API options list. diff --git a/core/lib/Drupal/Core/Field/FieldDefinitionListenerInterface.php b/core/lib/Drupal/Core/Field/FieldDefinitionListenerInterface.php new file mode 100644 index 0000000..c9c611e --- /dev/null +++ b/core/lib/Drupal/Core/Field/FieldDefinitionListenerInterface.php @@ -0,0 +1,47 @@ +isNew()) { // Notify the entity storage. - $entity_manager->getStorage($this->entity_type)->onFieldDefinitionCreate($this); + $entity_manager->onFieldDefinitionCreate($this); } else { // Some updates are always disallowed. @@ -160,7 +160,7 @@ public function preSave(EntityStorageInterface $storage) { throw new FieldException("Cannot change an existing field's storage."); } // Notify the entity storage. - $entity_manager->getStorage($this->entity_type)->onFieldDefinitionUpdate($this, $this->original); + $entity_manager->onFieldDefinitionUpdate($this, $this->original); } parent::preSave($storage); @@ -207,7 +207,7 @@ public static function postDelete(EntityStorageInterface $storage, array $fields // Notify the entity storage. foreach ($fields as $field) { if (!$field->deleted) { - \Drupal::entityManager()->getStorage($field->entity_type)->onFieldDefinitionDelete($field); + \Drupal::entityManager()->onFieldDefinitionDelete($field); } } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php index 5762e9e..cef7422 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php @@ -114,11 +114,11 @@ class EntityManagerTest extends UnitTestCase { protected $typedDataManager; /** - * The keyvalue collection for tracking installed definitions. + * The keyvalue factory. * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $installedDefinitions; + protected $keyValueFactory; /** * The event dispatcher. @@ -183,7 +183,7 @@ protected function setUp() { $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $this->installedDefinitions = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory = $this->getMock('Drupal\Core\KeyValueStore\KeyValueFactoryInterface'); $this->container = $this->getContainerWithCacheTagsInvalidator($this->cacheTagsInvalidator); $this->container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); @@ -239,7 +239,7 @@ protected function setUpEntityManager($definitions = array()) { ->method('getDefinitions') ->will($this->returnValue($definitions)); - $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cacheBackend, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions, $this->eventDispatcher); + $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cacheBackend, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->keyValueFactory, $this->eventDispatcher); $this->entityManager->setContainer($this->container); $this->entityManager->setDiscovery($this->discovery); } @@ -1247,7 +1247,7 @@ public function testGetFieldMap() { $id_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition') ->disableOriginalConstructor() ->getMock(); - $id_definition->expects($this->exactly(2)) + $id_definition->expects($this->once()) ->method('getType') ->will($this->returnValue('integer')); $base_field_definitions = array( @@ -1255,21 +1255,20 @@ public function testGetFieldMap() { ); $entity_class::$baseFieldDefinitions = $base_field_definitions; - // Set up a by bundle field definition that only exists on one bundle. - $bundle_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition') - ->disableOriginalConstructor() - ->getMock(); - $bundle_definition->expects($this->once()) - ->method('getType') - ->will($this->returnValue('string')); - $entity_class::$bundleFieldDefinitions = array( - 'test_entity_type' => array( - 'first_bundle' => array(), - 'second_bundle' => array( - 'by_bundle' => $bundle_definition, - ), - ), - ); + // Set up the stored bundle field map. + $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory->expects($this->once()) + ->method('get') + ->with('entity.definitions.field_map') + ->willReturn($key_value_store); + $key_value_store->expects($this->once()) + ->method('getAll') + ->willReturn(['test_entity_type' => [ + 'by_bundle' => [ + 'type' => 'string', + 'bundles' => ['second_bundle'], + ], + ]]); // Set up a non-content entity type. $non_content_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); @@ -1390,7 +1389,7 @@ public function testGetFieldMapByFieldType() { $id_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition') ->disableOriginalConstructor() ->getMock(); - $id_definition->expects($this->exactly(2)) + $id_definition->expects($this->once()) ->method('getType') ->will($this->returnValue('integer')); $base_field_definitions = array( @@ -1398,21 +1397,20 @@ public function testGetFieldMapByFieldType() { ); $entity_class::$baseFieldDefinitions = $base_field_definitions; - // Set up a by bundle field definition that only exists on one bundle. - $bundle_definition = $this->getMockBuilder('Drupal\Core\Field\BaseFieldDefinition') - ->disableOriginalConstructor() - ->getMock(); - $bundle_definition->expects($this->once()) - ->method('getType') - ->will($this->returnValue('string')); - $entity_class::$bundleFieldDefinitions = array( - 'test_entity_type' => array( - 'first_bundle' => array(), - 'second_bundle' => array( - 'by_bundle' => $bundle_definition, - ), - ), - ); + // Set up the stored bundle field map. + $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory->expects($this->once()) + ->method('get') + ->with('entity.definitions.field_map') + ->willReturn($key_value_store); + $key_value_store->expects($this->once()) + ->method('getAll') + ->willReturn(['test_entity_type' => [ + 'by_bundle' => [ + 'type' => 'string', + 'bundles' => ['second_bundle'], + ], + ]]); // Mock the base field definition override. $override_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); @@ -1444,6 +1442,271 @@ public function testGetFieldMapByFieldType() { } /** + * @covers ::onFieldDefinitionCreate + */ + public function testonFieldDefinitionCreateNewField() { + $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetEntityTypeId') + ->willReturn('test_entity_type'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetBundle') + ->willReturn('test_bundle'); + $field_definition->expects($this->atLeastOnce()) + ->method('getName') + ->willReturn('test_field'); + $field_definition->expects($this->atLeastOnce()) + ->method('getType') + ->willReturn('test_type'); + + $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface'); + + $class = get_class($storage); + $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getHandlerClass') + ->with('storage') + ->will($this->returnValue($class)); + $this->setUpEntityManager(array('test_entity_type' => $entity)); + + // The entity manager will instantiate a new object with the given class + // name. Define the mock expectations on that. + $storage = $this->entityManager->getStorage('test_entity_type'); + $storage->expects($this->once()) + ->method('onFieldDefinitionCreate') + ->with($field_definition); + + // Set up the stored bundle field map. + $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory->expects($this->exactly(2)) + ->method('get') + ->with('entity.definitions.field_map') + ->willReturn($key_value_store); + $key_value_store->expects($this->once()) + ->method('get') + ->with('test_entity_type') + ->willReturn([]); + $key_value_store->expects($this->once()) + ->method('set') + ->with('test_entity_type', [ + 'test_field' => [ + 'type' => 'test_type', + 'bundles' => ['test_bundle'], + ], + ]); + + $this->entityManager->onFieldDefinitionCreate($field_definition); + } + + /** + * @covers ::onFieldDefinitionCreate + */ + public function testonFieldDefinitionCreateExistingField() { + $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetEntityTypeId') + ->willReturn('test_entity_type'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetBundle') + ->willReturn('test_bundle'); + $field_definition->expects($this->atLeastOnce()) + ->method('getName') + ->willReturn('test_field'); + + $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface'); + $class = get_class($storage); + $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getHandlerClass') + ->with('storage') + ->will($this->returnValue($class)); + $this->setUpEntityManager(array('test_entity_type' => $entity)); + + // The entity manager will instantiate a new object with the given class + // name. Define the mock expectations on that. + $storage = $this->entityManager->getStorage('test_entity_type'); + $storage->expects($this->once()) + ->method('onFieldDefinitionCreate') + ->with($field_definition); + + // Set up the stored bundle field map. + $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory->expects($this->exactly(2)) + ->method('get') + ->with('entity.definitions.field_map') + ->willReturn($key_value_store); + $key_value_store->expects($this->once()) + ->method('get') + ->with('test_entity_type') + ->willReturn([ + 'test_field' => [ + 'type' => 'test_type', + 'bundles' => ['existing_bundle'], + ], + ]); + $key_value_store->expects($this->once()) + ->method('set') + ->with('test_entity_type', [ + 'test_field' => [ + 'type' => 'test_type', + 'bundles' => ['existing_bundle', 'test_bundle'], + ], + ]); + + $this->entityManager->onFieldDefinitionCreate($field_definition); + } + + /** + * @covers ::onFieldDefinitionUpdate + */ + public function testonFieldDefinitionUpdate() { + $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetEntityTypeId') + ->willReturn('test_entity_type'); + + $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface'); + + $class = get_class($storage); + $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getHandlerClass') + ->with('storage') + ->will($this->returnValue($class)); + $this->setUpEntityManager(array('test_entity_type' => $entity)); + + // The entity manager will instantiate a new object with the given class + // name. Define the mock expectations on that. + $storage = $this->entityManager->getStorage('test_entity_type'); + $storage->expects($this->once()) + ->method('onFieldDefinitionUpdate') + ->with($field_definition); + + $this->entityManager->onFieldDefinitionUpdate($field_definition, $field_definition); + } + + /** + * @covers ::onFieldDefinitionDelete + */ + public function testonFieldDefinitionDeleteMultipleBundles() { + $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetEntityTypeId') + ->willReturn('test_entity_type'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetBundle') + ->willReturn('test_bundle'); + $field_definition->expects($this->atLeastOnce()) + ->method('getName') + ->willReturn('test_field'); + + $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface'); + $class = get_class($storage); + $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getHandlerClass') + ->with('storage') + ->will($this->returnValue($class)); + $this->setUpEntityManager(array('test_entity_type' => $entity)); + + // The entity manager will instantiate a new object with the given class + // name. Define the mock expectations on that. + $storage = $this->entityManager->getStorage('test_entity_type'); + $storage->expects($this->once()) + ->method('onFieldDefinitionDelete') + ->with($field_definition); + + // Set up the stored bundle field map. + $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory->expects($this->exactly(2)) + ->method('get') + ->with('entity.definitions.field_map') + ->willReturn($key_value_store); + $key_value_store->expects($this->once()) + ->method('get') + ->with('test_entity_type') + ->willReturn([ + 'test_field' => [ + 'type' => 'test_type', + 'bundles' => ['test_bundle'], + ], + 'second_field' => [ + 'type' => 'test_type', + 'bundles' => ['test_bundle'], + ], + ]); + $key_value_store->expects($this->once()) + ->method('set') + ->with('test_entity_type', [ + 'second_field' => [ + 'type' => 'test_type', + 'bundles' => ['test_bundle'], + ], + ]); + + $this->entityManager->onFieldDefinitionDelete($field_definition); + } + + + /** + * @covers ::onFieldDefinitionDelete + */ + public function testonFieldDefinitionDeleteSingleBundles() { + $field_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetEntityTypeId') + ->willReturn('test_entity_type'); + $field_definition->expects($this->atLeastOnce()) + ->method('getTargetBundle') + ->willReturn('test_bundle'); + $field_definition->expects($this->atLeastOnce()) + ->method('getName') + ->willReturn('test_field'); + + $storage = $this->getMock('Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface'); + $class = get_class($storage); + $entity = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $entity->expects($this->once()) + ->method('getHandlerClass') + ->with('storage') + ->will($this->returnValue($class)); + $this->setUpEntityManager(array('test_entity_type' => $entity)); + + // The entity manager will instantiate a new object with the given class + // name. Define the mock expectations on that. + $storage = $this->entityManager->getStorage('test_entity_type'); + $storage->expects($this->once()) + ->method('onFieldDefinitionDelete') + ->with($field_definition); + + // Set up the stored bundle field map. + $key_value_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + $this->keyValueFactory->expects($this->exactly(2)) + ->method('get') + ->with('entity.definitions.field_map') + ->willReturn($key_value_store); + $key_value_store->expects($this->once()) + ->method('get') + ->with('test_entity_type') + ->willReturn([ + 'test_field' => [ + 'type' => 'test_type', + 'bundles' => ['test_bundle', 'second_bundle'], + ], + ]); + $key_value_store->expects($this->once()) + ->method('set') + ->with('test_entity_type', [ + 'test_field' => [ + 'type' => 'test_type', + 'bundles' => ['second_bundle'], + ], + ]); + + $this->entityManager->onFieldDefinitionDelete($field_definition); + } + + /** * @covers ::getEntityTypeFromClass */ public function testGetEntityTypeFromClass() {