Issue #2482295 by Berdir: Rebuilding field map with many bundles/fields is very slow
parent
b480c525ac
commit
0f881613ef
|
@ -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']
|
||||
|
|
|
@ -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 @@ use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
|
|||
*
|
||||
* 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.
|
||||
|
@ -30,37 +31,6 @@ interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStor
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -18,10 +18,12 @@ 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\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\KeyValueFactoryInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
|
@ -121,11 +123,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 +195,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 +211,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
$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 +644,43 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
$this->fieldMap = $cache->data;
|
||||
}
|
||||
else {
|
||||
// Rebuild the definitions and put it into the cache.
|
||||
// The field map is built in two steps. First, add all base fields, by
|
||||
// looping over all fieldable entity types. They always exist for all
|
||||
// bundles, and we do not expect to have so many different entity
|
||||
// types for this to become a bottleneck.
|
||||
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));
|
||||
foreach ($this->getBaseFieldDefinitions($entity_type_id) as $field_name => $base_field_definition) {
|
||||
$this->fieldMap[$entity_type_id][$field_name] = [
|
||||
'type' => $base_field_definition->getType(),
|
||||
'bundles' => array_combine($bundles, $bundles),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types', 'entity_field_info'));
|
||||
// In the second step, the per-bundle fields are added, based on the
|
||||
// persistent bundle field map stored in a key value collection. This
|
||||
// data is managed in the EntityManager::onFieldDefinitionCreate()
|
||||
// and EntityManager::onFieldDefinitionDelete() methods. Rebuilding this
|
||||
// information in the same way as base fields would not scale, as the
|
||||
// time to query would grow exponentially with more fields and bundles.
|
||||
// A cache would be deleted during cache clears, which is the only time
|
||||
// it is needed, so a key value collection is used.
|
||||
$bundle_field_maps = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->getAll();
|
||||
foreach ($bundle_field_maps as $entity_type_id => $bundle_field_map) {
|
||||
foreach ($bundle_field_map as $field_name => $map_entry) {
|
||||
if (!isset($this->fieldMap[$entity_type_id][$field_name])) {
|
||||
$this->fieldMap[$entity_type_id][$field_name] = $map_entry;
|
||||
}
|
||||
else {
|
||||
$this->fieldMap[$entity_type_id][$field_name]['bundles'] += $map_entry['bundles'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->cacheSet($cid, $this->fieldMap, Cache::PERMANENT, array('entity_types'));
|
||||
}
|
||||
}
|
||||
return $this->fieldMap;
|
||||
|
@ -679,6 +705,89 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
return $this->fieldMapByFieldType[$field_type];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition) {
|
||||
$entity_type_id = $field_definition->getTargetEntityTypeId();
|
||||
$bundle = $field_definition->getTargetBundle();
|
||||
$field_name = $field_definition->getName();
|
||||
|
||||
// Notify the storage about the new field.
|
||||
$this->getStorage($entity_type_id)->onFieldDefinitionCreate($field_definition);
|
||||
|
||||
// Update the bundle field map key value collection, add the new field.
|
||||
$bundle_field_map = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->get($entity_type_id);
|
||||
if (!isset($bundle_field_map[$field_name])) {
|
||||
// This field did not exist yet, initialize it with the type and empty
|
||||
// bundle list.
|
||||
$bundle_field_map[$field_name] = [
|
||||
'type' => $field_definition->getType(),
|
||||
'bundles' => [],
|
||||
];
|
||||
}
|
||||
$bundle_field_map[$field_name]['bundles'][$bundle] = $bundle;
|
||||
$this->keyValueFactory->get('entity.definitions.bundle_field_map')->set($entity_type_id, $bundle_field_map);
|
||||
|
||||
// Delete the cache entry.
|
||||
$this->cacheBackend->delete('entity_field_map');
|
||||
|
||||
// If the field map is initialized, update it as well, so that calls to it
|
||||
// do not have to rebuild it again.
|
||||
if ($this->fieldMap) {
|
||||
if (!isset($this->fieldMap[$entity_type_id][$field_name])) {
|
||||
// This field did not exist yet, initialize it with the type and empty
|
||||
// bundle list.
|
||||
$this->fieldMap[$entity_type_id][$field_name] = [
|
||||
'type' => $field_definition->getType(),
|
||||
'bundles' => [],
|
||||
];
|
||||
}
|
||||
$this->fieldMap[$entity_type_id][$field_name]['bundles'][$bundle] = $bundle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original) {
|
||||
// Notify the storage about the updated field.
|
||||
$this->getStorage($field_definition->getTargetEntityTypeId())->onFieldDefinitionUpdate($field_definition, $original);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) {
|
||||
$entity_type_id = $field_definition->getTargetEntityTypeId();
|
||||
$bundle = $field_definition->getTargetBundle();
|
||||
$field_name = $field_definition->getName();
|
||||
|
||||
// Notify the storage about the field deletion.
|
||||
$this->getStorage($entity_type_id)->onFieldDefinitionDelete($field_definition);
|
||||
|
||||
// Unset the bundle from the bundle field map key value collection.
|
||||
$bundle_field_map = $this->keyValueFactory->get('entity.definitions.bundle_field_map')->get($entity_type_id);
|
||||
unset($bundle_field_map[$field_name]['bundles'][$bundle]);
|
||||
if (empty($bundle_field_map[$field_name]['bundles'])) {
|
||||
// If there are no bundles left, remove the field from the map.
|
||||
unset($bundle_field_map[$field_name]);
|
||||
}
|
||||
$this->keyValueFactory->get('entity.definitions.bundle_field_map')->set($entity_type_id, $bundle_field_map);
|
||||
|
||||
// Delete the cache entry.
|
||||
$this->cacheBackend->delete('entity_field_map');
|
||||
|
||||
// If the field map is initialized, update it as well, so that calls to it
|
||||
// do not have to rebuild it again.
|
||||
if ($this->fieldMap) {
|
||||
unset($this->fieldMap[$entity_type_id][$field_name]['bundles'][$bundle]);
|
||||
if (empty($this->fieldMap[$entity_type_id][$field_name]['bundles'])) {
|
||||
unset($this->fieldMap[$entity_type_id][$field_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds field storage definitions for an entity type.
|
||||
*
|
||||
|
@ -1243,7 +1352,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
* {@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 +1376,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
*/
|
||||
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 +1386,18 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
* 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 +1409,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,12 +9,13 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
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.
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Field\FieldDefinitionListenerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Field;
|
||||
|
||||
/**
|
||||
* Defines an interface for reacting to field creation, deletion, and updates.
|
||||
*/
|
||||
interface FieldDefinitionListenerInterface {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition);
|
||||
|
||||
}
|
|
@ -160,7 +160,7 @@ class FieldConfig extends FieldConfigBase implements FieldConfigInterface {
|
|||
|
||||
if ($this->isNew()) {
|
||||
// Notify the entity storage.
|
||||
$entity_manager->getStorage($this->entity_type)->onFieldDefinitionCreate($this);
|
||||
$entity_manager->onFieldDefinitionCreate($this);
|
||||
}
|
||||
else {
|
||||
// Some updates are always disallowed.
|
||||
|
@ -174,7 +174,7 @@ class FieldConfig extends FieldConfigBase implements FieldConfigInterface {
|
|||
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);
|
||||
|
@ -221,7 +221,7 @@ class FieldConfig extends FieldConfigBase implements FieldConfigInterface {
|
|||
// Notify the entity storage.
|
||||
foreach ($fields as $field) {
|
||||
if (!$field->deleted) {
|
||||
\Drupal::entityManager()->getStorage($field->entity_type)->onFieldDefinitionDelete($field);
|
||||
\Drupal::entityManager()->onFieldDefinitionDelete($field);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,11 @@ class EntityBundleFieldTest extends EntityUnitTestBase {
|
|||
'type' => 'custom',
|
||||
]);
|
||||
$this->assertTrue($entity->hasField('custom_bundle_field'));
|
||||
|
||||
// Ensure that the field exists in the field map.
|
||||
$field_map = \Drupal::entityManager()->getFieldMap();
|
||||
$this->assertEqual($field_map['entity_test']['custom_bundle_field'], ['type' => 'string', 'bundles' => ['custom' => 'custom']]);
|
||||
|
||||
$entity->custom_bundle_field->value = 'swanky';
|
||||
$entity->save();
|
||||
$storage->resetCache();
|
||||
|
@ -102,6 +107,10 @@ class EntityBundleFieldTest extends EntityUnitTestBase {
|
|||
->execute();
|
||||
$this->assertEqual(1, $result->fetchField(), 'Field data has been deleted');
|
||||
|
||||
// Ensure that the field no longer exists in the field map.
|
||||
$field_map = \Drupal::entityManager()->getFieldMap();
|
||||
$this->assertFalse(isset($field_map['entity_test']['custom_bundle_field']));
|
||||
|
||||
// @todo Test field purge and table deletion once supported. See
|
||||
// https://www.drupal.org/node/2282119.
|
||||
// $this->assertFalse($this->database->schema()->tableExists($table), 'Custom field table was deleted');
|
||||
|
|
|
@ -67,6 +67,21 @@ function entity_schema_test_entity_bundle_field_info(EntityTypeInterface $entity
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_bundle_create().
|
||||
*/
|
||||
function entity_schema_test_entity_bundle_create($entity_type_id, $bundle) {
|
||||
if ($entity_type_id == 'entity_test' && $bundle == 'custom') {
|
||||
$entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
|
||||
$field_definitions = entity_schema_test_entity_bundle_field_info($entity_type, $bundle);
|
||||
$field_definitions['custom_bundle_field']
|
||||
->setTargetEntityTypeId($entity_type_id)
|
||||
->setTargetBundle($bundle);
|
||||
// Notify the entity storage that we just created a new field.
|
||||
\Drupal::entityManager()->onFieldDefinitionCreate($field_definitions['custom_bundle_field']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_bundle_delete().
|
||||
*/
|
||||
|
@ -78,7 +93,6 @@ function entity_schema_test_entity_bundle_delete($entity_type_id, $bundle) {
|
|||
->setTargetEntityTypeId($entity_type_id)
|
||||
->setTargetBundle($bundle);
|
||||
// Notify the entity storage that our field is gone.
|
||||
\Drupal::entityManager()->getStorage($entity_type_id)
|
||||
->onFieldDefinitionDelete($field_definitions['custom_bundle_field']);
|
||||
\Drupal::entityManager()->onFieldDefinitionDelete($field_definitions['custom_bundle_field']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 @@ class EntityManagerTest extends UnitTestCase {
|
|||
|
||||
$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 @@ class EntityManagerTest extends UnitTestCase {
|
|||
->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);
|
||||
}
|
||||
|
@ -1250,7 +1250,7 @@ class EntityManagerTest extends UnitTestCase {
|
|||
$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(
|
||||
|
@ -1258,21 +1258,20 @@ class EntityManagerTest extends UnitTestCase {
|
|||
);
|
||||
$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.bundle_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' => 'second_bundle'],
|
||||
],
|
||||
]]);
|
||||
|
||||
// Set up a non-content entity type.
|
||||
$non_content_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
|
||||
|
@ -1303,11 +1302,11 @@ class EntityManagerTest extends UnitTestCase {
|
|||
'test_entity_type' => array(
|
||||
'id' => array(
|
||||
'type' => 'integer',
|
||||
'bundles' => array('first_bundle', 'second_bundle'),
|
||||
'bundles' => array('first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'),
|
||||
),
|
||||
'by_bundle' => array(
|
||||
'type' => 'string',
|
||||
'bundles' => array('second_bundle'),
|
||||
'bundles' => array('second_bundle' => 'second_bundle'),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
@ -1322,11 +1321,11 @@ class EntityManagerTest extends UnitTestCase {
|
|||
'test_entity_type' => array(
|
||||
'id' => array(
|
||||
'type' => 'integer',
|
||||
'bundles' => array('first_bundle', 'second_bundle'),
|
||||
'bundles' => array('first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle'),
|
||||
),
|
||||
'by_bundle' => array(
|
||||
'type' => 'string',
|
||||
'bundles' => array('second_bundle'),
|
||||
'bundles' => array('second_bundle' => 'second_bundle'),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
@ -1393,7 +1392,7 @@ class EntityManagerTest extends UnitTestCase {
|
|||
$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(
|
||||
|
@ -1401,21 +1400,20 @@ class EntityManagerTest extends UnitTestCase {
|
|||
);
|
||||
$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.bundle_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' => 'second_bundle'],
|
||||
],
|
||||
]]);
|
||||
|
||||
// Mock the base field definition override.
|
||||
$override_entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
|
||||
|
@ -1446,6 +1444,271 @@ class EntityManagerTest extends UnitTestCase {
|
|||
$this->assertArrayNotHasKey('id', $stringFields['test_entity_type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.bundle_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' => '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.bundle_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' => 'existing_bundle'],
|
||||
],
|
||||
]);
|
||||
$key_value_store->expects($this->once())
|
||||
->method('set')
|
||||
->with('test_entity_type', [
|
||||
'test_field' => [
|
||||
'type' => 'test_type',
|
||||
'bundles' => ['existing_bundle' => 'existing_bundle', 'test_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.bundle_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' => 'test_bundle'],
|
||||
],
|
||||
'second_field' => [
|
||||
'type' => 'test_type',
|
||||
'bundles' => ['test_bundle' => 'test_bundle'],
|
||||
],
|
||||
]);
|
||||
$key_value_store->expects($this->once())
|
||||
->method('set')
|
||||
->with('test_entity_type', [
|
||||
'second_field' => [
|
||||
'type' => 'test_type',
|
||||
'bundles' => ['test_bundle' => '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.bundle_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' => 'test_bundle', 'second_bundle' => 'second_bundle'],
|
||||
],
|
||||
]);
|
||||
$key_value_store->expects($this->once())
|
||||
->method('set')
|
||||
->with('test_entity_type', [
|
||||
'test_field' => [
|
||||
'type' => 'test_type',
|
||||
'bundles' => ['second_bundle' => 'second_bundle'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->entityManager->onFieldDefinitionDelete($field_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getEntityTypeFromClass
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue