From da2c85be9264962bb6fb24b5609c982a20241b2b Mon Sep 17 00:00:00 2001 From: Dave Long <dave@longwaveconsulting.com> Date: Tue, 12 Nov 2024 14:38:46 +0000 Subject: [PATCH] Issue #3310170 by bradjones1, tstoeckler, smustgrave, thursday_bw: Use UUID as entity ID --- .../Core/Entity/Sql/DefaultTableMapping.php | 11 ++- .../modules/entity_test/entity_test.module | 1 + .../src/Entity/EntityTestUuidId.php | 77 +++++++++++++++++++ .../Entity/EntityUuidIdTest.php | 71 +++++++++++++++++ 4 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUuidId.php create mode 100644 core/tests/Drupal/FunctionalTests/Entity/EntityUuidIdTest.php diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php index f8f759f05521..93398a56fc3b 100644 --- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php +++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php @@ -176,7 +176,9 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st return $table_mapping->allowsSharedTableStorage($definition); }); - $key_fields = array_values(array_filter([$id_key, $revision_key, $bundle_key, $uuid_key, $langcode_key])); + // The ID and UUID key may point to the same field, so make sure the list is + // unique. + $key_fields = array_values(array_unique(array_filter([$id_key, $revision_key, $bundle_key, $uuid_key, $langcode_key]))); $all_fields = array_keys($shared_table_definitions); $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) { return $definition->isRevisionable(); @@ -206,10 +208,11 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st // whether they are translatable or not. The data table holds also a // denormalized copy of the bundle field value to allow for more // performant queries. This means that only the UUID is not stored on - // the data table. + // the data table. Make sure the ID is always in the list, even if the ID + // key and the UUID key point to the same field. $table_mapping ->setFieldNames($table_mapping->baseTable, $key_fields) - ->setFieldNames($table_mapping->dataTable, array_values(array_diff($all_fields, [$uuid_key]))); + ->setFieldNames($table_mapping->dataTable, array_values(array_unique(array_merge([$id_key], array_diff($all_fields, [$uuid_key]))))); } elseif ($revisionable && $translatable) { // The revisionable multilingual layout stores key field values in the @@ -224,7 +227,7 @@ public static function create(ContentEntityTypeInterface $entity_type, array $st // Like in the multilingual, non-revisionable case the UUID is not // in the data table. Additionally, do not store revision metadata // fields in the data table. - $data_fields = array_values(array_diff($all_fields, [$uuid_key], $revision_metadata_fields)); + $data_fields = array_values(array_unique(array_merge([$id_key], array_diff($all_fields, [$uuid_key], $revision_metadata_fields)))); $table_mapping->setFieldNames($table_mapping->dataTable, $data_fields); $revision_base_fields = array_merge([$id_key, $revision_key, $langcode_key], $revision_metadata_fields); diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index ea44a68c54fd..20e30117f766 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -73,6 +73,7 @@ function entity_test_entity_types($filter = NULL) { if ($filter === ENTITY_TEST_TYPES_ROUTING) { $types[] = 'entity_test_base_field_display'; $types[] = 'entity_test_string_id'; + $types[] = 'entity_test_uuid_id'; $types[] = 'entity_test_no_id'; $types[] = 'entity_test_mul_with_bundle'; } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUuidId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUuidId.php new file mode 100644 index 000000000000..5935fd857ded --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUuidId.php @@ -0,0 +1,77 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\entity_test\Entity; + +use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; + +/** + * Defines a test entity class with UUIDs as IDs. + * + * @ContentEntityType( + * id = "entity_test_uuid_id", + * label = @Translation("Test entity with UUIDs as IDs"), + * handlers = { + * "access" = "Drupal\entity_test\EntityTestAccessControlHandler", + * "form" = { + * "default" = "Drupal\entity_test\EntityTestForm" + * }, + * "route_provider" = { + * "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", + * }, + * }, + * translatable = TRUE, + * base_table = "entity_test_uuid_id", + * data_table = "entity_test_uuid_id_data", + * admin_permission = "administer entity_test content", + * entity_keys = { + * "id" = "uuid", + * "uuid" = "uuid", + * "bundle" = "type", + * "langcode" = "langcode", + * "label" = "name", + * }, + * links = { + * "canonical" = "/entity_test_uuid_id/manage/{entity_test_uuid_id}", + * "add-form" = "/entity_test_uuid_id/add", + * "edit-form" = "/entity_test_uuid_id/manage/{entity_test_uuid_id}/edit", + * }, + * ) + */ +class EntityTestUuidId extends EntityTest { + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + // Configure a string field to match the UUID field configuration and use it + // for both the ID and the UUID key. The UUID field type cannot be used + // because it would add a unique key to the data table. + $fields[$entity_type->getKey('uuid')] = BaseFieldDefinition::create('string') + ->setLabel(new TranslatableMarkup('UUID')) + /* @see \Drupal\Core\Field\Plugin\Field\FieldType\UuidItem::defaultStorageSettings() */ + ->setSetting('max_length', 128) + ->setSetting('is_ascii', TRUE) + /* @see \Drupal\Core\Field\Plugin\Field\FieldType\UuidItem::applyDefaultValue() */ + ->setDefaultValueCallback(static::class . '::generateUuid'); + return $fields; + } + + /** + * Statically generates a UUID. + * + * @return string + * A newly generated UUID. + */ + public static function generateUuid(): string { + $uuid = \Drupal::service('uuid'); + assert($uuid instanceof UuidInterface); + return $uuid->generate(); + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Entity/EntityUuidIdTest.php b/core/tests/Drupal/FunctionalTests/Entity/EntityUuidIdTest.php new file mode 100644 index 000000000000..d6bdff7c090d --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Entity/EntityUuidIdTest.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\FunctionalTests\Entity; + +use Drupal\Component\Uuid\Uuid; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait; + +/** + * Tests that an entity with a UUID as ID can be managed. + * + * @group Entity + */ +class EntityUuidIdTest extends BrowserTestBase { + + use ContentTranslationTestTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = ['block', 'content_translation', 'entity_test']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->createLanguageFromLangcode('af'); + $this->enableContentTranslation('entity_test_uuid_id', 'entity_test_uuid_id'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalPlaceBlock('local_tasks_block'); + } + + /** + * Tests the user interface for the test entity. + */ + public function testUi(): void { + $this->drupalLogin($this->createUser([ + 'administer entity_test content', + 'create content translations', + 'translate entity_test_uuid_id', + 'view test entity', + ])); + + // Test adding an entity. + $this->drupalGet('/entity_test_uuid_id/add'); + $this->submitForm([ + 'Name' => 'Test entity with UUID ID', + ], 'Save'); + $this->assertSession()->elementTextEquals('css', 'h1', 'Edit Test entity with UUID ID'); + $this->assertSession()->addressMatches('#^/entity_test_uuid_id/manage/' . Uuid::VALID_PATTERN . '/edit$#'); + + // Test translating an entity. + $this->clickLink('Translate'); + $this->clickLink('Add'); + $this->submitForm([ + 'Name' => 'Afrikaans translation of test entity with UUID ID', + ], 'Save'); + $this->assertSession()->elementTextEquals('css', 'h1', 'Afrikaans translation of test entity with UUID ID [Afrikaans translation]'); + $this->assertSession()->addressMatches('#^/af/entity_test_uuid_id/manage/' . Uuid::VALID_PATTERN . '/edit$#'); + } + +} -- GitLab