Problem/Motivation

It would be great to provide URLs with UUIDs in their so you can link to them.

The solution would need to meet the following requirements:

  • Allow a UUID-based URL to be used in menus, end-user-facing links should use a path alias when available
  • Allow a UUID-based URL to be used for front page, 403 and 404 pages, without strange redirection by-effects
  • Allow a UUID-based URL to be used as in the page condition for e.g. block-placement. It should not make a difference if the alias-, numeric ID- or UUID-based URL is used, the block should be shown regardless of the URL used to access the content
  • The "duplicate content" situation is not made worse then it is, i.e. search engines should be guided to a canonical URL somehow, either by redirects (for aliases and traditional numeric URLs this is handled by Redirect module in contrib) or a canonical metatag

Proposed resolution

The most recent solution uses a path converter. It relies on the existing behaviour of setting the canonical meta tag to avoid duplicate content issues in terms of SEO (because that is already the approach taken with aliases vs. system paths). Internally, the path is converted to the numeric path, so that anything relying on that also works for the UUID-based URL.

Add a new uuid link template for entities:

$node->toUrl('uuid');

Remaining tasks

  • [x] Add special case code to Entity::toUrl() to handle 'uuid'
  • ❓Test all use cases
  • [ ] Update CR

User interface changes

The UUID-based URL is available.

API changes

It is possible to request the UUID-based URL for an entity.

CommentFileSizeAuthor
#289 interdiff_288-289.txt778 bytesvsujeetkumar
#289 2353611-289.patch13.44 KBvsujeetkumar
#288 interdiff_287-288.txt1.72 KBvsujeetkumar
#288 2353611-288.patch13.49 KBvsujeetkumar
#287 diff_262-287.txt3.68 KBvsujeetkumar
#287 2353611-287.patch13.48 KBvsujeetkumar
#262 drupal-link_uuid-2353611-262.patch13.53 KBerik frèrejean
#262 interdiff_260_262.txt2.73 KBerik frèrejean
#260 drupal-link_uuid-2353611-260.patch13.5 KBerik frèrejean
#260 interdiff_256_260.txt465 byteserik frèrejean
#257 blog_categories.txt8.87 KBJayBeeDutch
#256 drupal-link_uuid-2353611-256.patch13.5 KBmrweiner
#256 interdiff_248-256.txt1.76 KBmrweiner
#248 interdiff-2353611-247-248.patch2.67 KBeelkeblok
#248 drupal-link_uuid-2353611-248-d8.patch13.49 KBeelkeblok
#247 interdiff-242-247.txt16.88 KBeelkeblok
#247 drupal-link_uuid-2353611-247-d8.patch14.41 KBeelkeblok
#242 drupal-link_uuid-2353611-242-d8.patch30.6 KBeelkeblok
#241 interdiff-235-241.txt11.67 KBeelkeblok
#241 drupal-link_uuid-2353611-241-d8.patch30.57 KBeelkeblok
#235 2353611-235.patch18.92 KBmanuel garcia
#235 interdiff-2353611-233-235.txt4.25 KBmanuel garcia
#233 interdiff_215-233.txt1.46 KBricovandevin
#233 drupal-provide-entity-uuid-routes-2353611-233-D8.patch18.9 KBricovandevin
#227 2353611-227.patch18.24 KBmahtab_alam
#218 2353611-218.patch18.31 KBvalthebald
#218 interdiff-2353611-215-218.txt1.59 KBvalthebald
#215 2353611-215.patch18.29 KBvalthebald
#213 2353611-213.patch21.3 KBvalthebald
#211 2353611-211.patch30.65 KBvalthebald
#209 2353611-209.patch30.6 KBvalthebald
#197 2353611-197.patch53.39 KBvalthebald
#197 interdiff-2353611-195-197.txt1.07 KBvalthebald
#195 2353611-195.patch53.57 KBvalthebald
#193 interdiff-2353611-186-193.txt40.99 KBvalthebald
#193 2353611-193.patch51.79 KBvalthebald
#186 2353611-186.patch85.3 KBreshma_n
#179 drupal-link_uuid-2353611-179.patch63.36 KBgrimreaper
#161 2353611-161.patch62.93 KBwim leers
#161 interdiff.txt561 byteswim leers
#159 interdiff.txt8.67 KBwim leers
#159 2353611-158.patch62.92 KBwim leers
#157 interdiff.txt711 byteswim leers
#157 2353611-157.patch60.78 KBwim leers
#156 rebase-interdiff.txt623 byteswim leers
#156 2353611-156.patch60.11 KBwim leers
#154 2353611-154.patch60.2 KBwim leers
#154 interdiff.txt1.01 KBwim leers
#152 2353611-150.patch57.91 KBwim leers
#150 147-150-interdiff.txt823 bytesacbramley
#150 2353611-150.patch57.91 KBacbramley
#149 129-147-interdiff.txt593 bytesacbramley
#147 2353611-147.patch57.91 KBacbramley
#129 2353611-3-129.patch58 KBalexpott
#129 127-129-interdiff.txt1.35 KBalexpott
#127 2353611-3-127.patch57.94 KBalexpott
#127 126-127-interdiff.txt2.73 KBalexpott
#126 2353611-3-126.patch58.23 KBalexpott
#126 122-126-interdiff.txt2.58 KBalexpott
#122 2353611-2-uuid-link.122.patch58.18 KBlarowlan
#122 interdiff.txt555 byteslarowlan
#119 2353611-2-uuid-link.119.patch57.64 KBlarowlan
#119 interdiff.txt1.82 KBlarowlan
#116 2353611-2-uuid-link.116.patch57.65 KBlarowlan
#116 interdiff.txt9.31 KBlarowlan
#109 2353611-2-108.patch52.65 KBalexpott
#109 107-108-interdiff.txt12.59 KBalexpott
#107 2353611-2-107.patch52.5 KBalexpott
#107 106-107-interdiff.txt5.04 KBalexpott
#106 2353611-2-106.patch49.58 KBalexpott
#106 91-106-interdiff.txt9.43 KBalexpott
#102 interdiff-2353611-100-102.txt683 bytesdaffie
#102 interdiff-2353611-91-102.txt1.79 KBdaffie
#102 2353611-102.patch44.58 KBdaffie
#100 2353611-100.patch44.59 KBdaffie
#100 interdiff-2353611-91-100.txt1.13 KBdaffie
#91 2353611-91.patch44.38 KBalexpott
#89 2353611-uuid-link.89.patch44.34 KBlarowlan
#89 interdiff.txt4.8 KBlarowlan
#85 2353611-uuid-link.85.patch44.24 KBlarowlan
#85 interdiff.txt885 byteslarowlan
#83 2353611-uuid-link.83.patch44.15 KBlarowlan
#83 interdiff.txt848 byteslarowlan
#77 2353611-uuid-link.77.patch44.16 KBlarowlan
#77 interdiff.txt12.12 KBlarowlan
#74 2353611-uuid-link.74.patch36.41 KBlarowlan
#74 interdiff.txt1.15 KBlarowlan
#72 2353611-uuid-link.72.patch36.07 KBlarowlan
#72 interdiff.txt1.24 KBlarowlan
#67 2353611-uuid-link.67.patch36.06 KBlarowlan
#67 interdiff.txt22.94 KBlarowlan
#56 2353611-uuid-link.56.patch28.02 KBlarowlan
#56 interdiff.txt6.34 KBlarowlan
#52 2353611-uuid-link.52.patch25.89 KBlarowlan
#52 interdiff.txt726 byteslarowlan
#50 2353611-uuid-link.50.patch25.71 KBlarowlan
#50 interdiff.txt5.54 KBlarowlan
#48 interdiff.txt3.72 KBlarowlan
#47 2353611-uuid-link.47.patch23.27 KBlarowlan
#45 2353611-uuid-link.45.patch20.26 KBlarowlan
#45 interdiff.txt25.01 KBlarowlan
#34 2353611-uuid-link.34.patch15.83 KBlarowlan
#34 interdiff.txt29.92 KBlarowlan
#26 link-entity-uuid-2353611.26-do-not-test.patch9.54 KBsime
#22 link-entity-uuid-2353611.22.patch22.67 KBsime
#15 link-entity-uuid-2353611.15.patch11.24 KBlarowlan
#15 interdiff.txt8.93 KBlarowlan
#11 link-entity-uuid-2353611.11.patch5.88 KBlarowlan
#6 2353611-temp.txt5.86 KBdawehner
#5 2353611-5.patch3.23 KBdawehner
#4 ef_uuid.zip3.12 KBdawehner
#2 2353611-2.patch2.93 KBdawehner

Issue fork drupal-2353611

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

chx’s picture

Absolutely lovely idea, I do not even know why that's not the default :)

dawehner’s picture

Status: Active » Needs review
StatusFileSize
new2.93 KB

This makes it at least possible to link to it.

Status: Needs review » Needs work

The last submitted patch, 2: 2353611-2.patch, failed testing.

dawehner’s picture

StatusFileSize
new3.12 KB

Just in case someone is already interested, here is a small module implementing this kind of functionality.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new3.23 KB

Just fixing hopefully all the failures for now.

dawehner’s picture

StatusFileSize
new5.86 KB

.

clemens.tolboom’s picture

Crell’s picture

The proposal in the summary sounds great: A 301 is the right way to do this.

However, it looks like the patch is not actually setting that up yet. IMO we should put the uuid version at a different URI than the auto-inc version. In fact, it doesn't even need to be in REST. Any entity with a canonical URI can also get a /uuid/{type}/{uuid} URI that does nothing but send a redirect.

dawehner’s picture

Issue tags: +uuid-everywhere

Adding the tag.

clemens.tolboom’s picture

Status: Needs review » Needs work

Checking #6 and remarks from #8 this needs work.

Is #6 supposed to be a patch maybe?

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new5.88 KB

first a reroll, then tackling #10

Status: Needs review » Needs work

The last submitted patch, 11: link-entity-uuid-2353611.11.patch, failed testing.

larowlan’s picture

this died horribly because OutboundRouteProcessorInterface changed, and invalid yml, guess that explains #6 being a txt file :)

larowlan’s picture

Assigned: Unassigned » larowlan

working on this a bit

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new8.93 KB
new11.24 KB

Wow Drupal\menu_ui\Tests\MenuTest is a big one, 7 mins per run locally.

Added new integration tests to it covering the new behaviour, made sure that menu links pointing to nodes by UUID are picked up by the node edit form alters in menu_ui module.

There is no redirect because the path processor lets you use serial ID (nid) or UUID. Redirect would be the job of global redirect module (we don't redirect node/2 to /some/pretty/path in core).

Had to do some refactoring in the menu tree manipulator because sometimes the node parameter is a uuid.

Status: Needs review » Needs work

The last submitted patch, 15: link-entity-uuid-2353611.15.patch, failed testing.

The last submitted patch, 11: link-entity-uuid-2353611.11.patch, failed testing.

larowlan’s picture

Assigned: larowlan » Unassigned

until tomorrow in case anyone else is inclined

The last submitted patch, 15: link-entity-uuid-2353611.15.patch, failed testing.

skwashd’s picture

It is great to see this functionality being incorporated into D8. This is something the D7 contrib module already does.

wim leers’s picture

Version: 8.0.x-dev » 8.1.x-dev
Category: Task » Feature request
sime’s picture

StatusFileSize
new22.67 KB

Updating patch from #15.

wim leers’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 22: link-entity-uuid-2353611.22.patch, failed testing.

sime’s picture

The last patch is broken.

FWIW, I've been working on this against the 8.0.x branch (so this might not a be a problem on the 8.1.x branch) and I'm having some issues getting past a route exception:

Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: No route found for "GET /node/cf4048f3-51b3-4a9a-8918-969929c175d8": Method Not Allowed (Allow: DELETE, PATCH) in Symfony\Component\HttpKernel\EventListener\RouterListener->onKernelRequest() (line 164 of /var/www/site/vendor/symfony/http-kernel/EventListener/RouterListener.php).
sime’s picture

StatusFileSize
new9.54 KB

This patch is against 8.0.x. It's basically #15 plus some hacks to make it work, and I removed the tests. I'm not setting this to issue "needs review" but maybe the patch is useful - the comments below may not even apply to 8.1.x.

So, firstly the route expects an integer so using node/{uuid} is rejected before it gets to RouteProcessorEntityUUID.php. See ->setRequirement('node', '\d+') in NodeRouteProvider.php

Also in the patch in #15, the /node/{uuid} has no logic for redirecting to /node/{id} - I think this would be mandatory. I worked around this with a redirect in the NodeViewController. I don't know the best fix for this.

I'm not sure what the menu-related changes in #15 patch do, I left them in... perhaps they are only relevant if we don't do a redirect?

sime’s picture

Issue summary: View changes

Modified description to reflect the recent patches.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Anyone wants to discuss what / how we actually want to implement it?

larowlan’s picture

Yeah, will try and catch you tomorrow

larowlan’s picture

Another option here is to add a new link template to go with the 301 redirect.

$entity->toUrl('uuid');
larowlan’s picture

Discussed with @dawehner - I'm going to do the following

  • Update to use inbound path processor instead
  • Update tests
  • Profile
larowlan’s picture

Assigned: Unassigned » larowlan

Working on this

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new29.92 KB
new15.83 KB

Interdiff is against #22

Let's see what fails here.

benjy’s picture

  1. +++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorEntityUuid.php
    @@ -0,0 +1,58 @@
    +        if ($entity_type->hasLinkTemplate('canonical') && $entities = $storage->loadByProperties(['uuid' => $uuid])) {
    

    Not sure but might be worth injecting EntityRepository for loadEntityByUuid here

  2. +++ b/core/modules/menu_ui/menu_ui.module
    +++ b/core/modules/menu_ui/menu_ui.module
    @@ -201,8 +201,12 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    
    @@ -201,8 +201,12 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    +      $query = \Drupal::entityQuery('menu_link_content');
    

    Does this need an upgrade path?

dawehner’s picture

Just a quick thought:

+++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorEntityUuid.php
@@ -0,0 +1,58 @@
+    if (preg_match('/^\/([a-z_]+)\/(' . Uuid::VALID_PATTERN . ')$/i', $path, $matches)) {

So given that we have a static pattern we could improve the situation for all other routes, by registering one route per entity type together with the UUID pattern as requirement.

larowlan’s picture

@dawehner so we'd add that to \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider for any entity-type with a canonical link template?

Makes sense to me.

Would it be worth adding a uuid link-template to go with it, that'd make it easier to use - instead of internal:/{entity_type}/{uuid} we could use $entity->toUrl('uuid');

Sound about right?

dawehner’s picture

+1 from me.

wim leers’s picture

Would it be worth adding a uuid link-template to go with it

+1

internal:/{entity_type}/{uuid}

Why not entity:{entity type}/{entity uuid}?

timmillwood’s picture

We need a uuid index, this is something we have added as part of Multiversion.

I am proposing this for Entity module (#2690745: Create an index of UUIDs) and core (#2690747: [PLAN] Create an index of UUIDs), essentially it's just a big key_value store keyed by UUID and stores entity type id, entity id, and potentially other things.

Then, as we do in Multiversion and other modules that depend on it, we query the index with just the UUID and we can load the entity from the entity type manager.

larowlan’s picture

@timmillwood whilst I agree a uuid index is useful, I don't think we need it here, we have the entity_type information.

timmillwood’s picture

@larowlan - Right, this issue introduces incoming paths matching /{entity_type_id}/{uuid}, but wouldn't it be cool to introduce Incoming paths matching /{uuid}. In that case we would need a UUID index.

In Relaxed module we use paths with the uuid but not the entity type, therefore we make use of the uuid index in Multiversion.

dawehner’s picture

Issue tags: +DrupalCampES

/{uuid}

I mean technically we decided against having such URLs, so we always at least some prefix, but generically I kinda dislike to totally have missing semantic information about what is contained behind those resources.

larowlan’s picture

working on this

larowlan’s picture

StatusFileSize
new25.01 KB
new20.26 KB

current patch, some failing tests at this stage, will continue on tuesday

Status: Needs review » Needs work

The last submitted patch, 45: 2353611-uuid-link.45.patch, failed testing.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new23.27 KB

More work on failing tests

larowlan’s picture

StatusFileSize
new3.72 KB

Status: Needs review » Needs work

The last submitted patch, 47: 2353611-uuid-link.47.patch, failed testing.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new5.54 KB
new25.71 KB

More work on failing tests

Status: Needs review » Needs work

The last submitted patch, 50: 2353611-uuid-link.50.patch, failed testing.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new726 bytes
new25.89 KB

more fixes

dawehner’s picture

Nice, a green patch!

  1. +++ b/core/lib/Drupal/Core/Entity/Entity.php
    --- a/core/lib/Drupal/Core/Entity/EntityTypeManager.php
    +++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
    
    +++ b/core/modules/user/src/Entity/UserRouteProvider.php
    @@ -2,21 +2,33 @@
    diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    
    diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    index c2325d4..6c96e79 100644
    
    index c2325d4..6c96e79 100644
    --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    
    --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    
    +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    @@ -99,6 +99,14 @@ protected function setUpEntityTypeDefinitions($definitions = []) {
    
    @@ -99,6 +99,14 @@ protected function setUpEntityTypeDefinitions($definitions = []) {
           // Give the entity type a legitimate class to return.
           $entity_type->getClass()->willReturn($class);
     
    +      // Don't allow uuid key.
    +      $entity_type->hasKey('uuid')->willReturn(FALSE);
    +
    +      // Or route provider.
    +      if (!$entity_type->getMethodProphecies('getRouteProviderClasses')) {
    +        $entity_type->getRouteProviderClasses()->willReturn([]);
    +      }
    +
           $definitions[$key] = $entity_type->reveal();
         }
     
    

    Do you think we should expand the test coverage of the entity type manager here?

  2. +++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
    @@ -83,6 +84,12 @@ public function processDefinition(&$definition, $plugin_id) {
    +    if ($definition->hasKey('uuid') && $definition->hasViewBuilderClass() && $has_uuid_route) {
    +      $definition->setLinkTemplate('uuid', "/{$plugin_id}/{{$plugin_id}}");
    +    }
    

    Should we respect previously defined UUID templates?

  3. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -23,12 +24,13 @@
    -class DefaultHtmlRouteProvider implements EntityRouteProviderInterface, EntityHandlerInterface {
    +class DefaultHtmlRouteProvider implements EntityUuidRouteProviderInterface, EntityHandlerInterface {
    
    +++ b/core/lib/Drupal/Core/Entity/Routing/EntityUuidRouteProviderInterface.php
    @@ -0,0 +1,10 @@
    +<?php
    +
    +namespace Drupal\Core\Entity\Routing;
    +
    +/**
    + * Defines an interface for route providers that support a UUID link template.
    + */
    +interface EntityUuidRouteProviderInterface extends EntityRouteProviderInterface {
    +
    +}
    

    Do we need that?

  4. +++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
    @@ -151,9 +154,13 @@ public function checkNodeAccess(array $tree) {
           $nids = $query->execute();
    -      foreach ($nids as $nid) {
    -        foreach ($node_links[$nid] as $key => $link) {
    -          $node_links[$nid][$key]->access = $access_result;
    +      if ($nids) {
    +        foreach ($nids as $nid) {
    +          if (isset($node_links[$nid])) {
    +            foreach ($node_links[$nid] as $key => $link) {
    +              $node_links[$nid][$key]->access = $access_result;
    +            }
    +          }
             }
           }
    

    We could prevent the if() by casting it to an array first. Not sure which you though you prefer.

  5. +++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
    @@ -2,6 +2,7 @@
    @@ -62,6 +63,12 @@ public function convert($value, $definition, $name, array $defaults) {
    
    @@ -62,6 +63,12 @@ public function convert($value, $definition, $name, array $defaults) {
         $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
         if ($storage = $this->entityManager->getStorage($entity_type_id)) {
           $entity = $storage->load($value);
    +      // If there is no entity loadable by ID, try to load by UUID.
    +      if (!$entity && Uuid::isValid($value)) {
    +        if ($entities = $storage->loadByProperties(['uuid' => $value])) {
    +          $entity = reset($entities);
    +        }
    +      }
    

    Can we document on the convert method that the value might also be a uuid?

  6. +++ b/core/modules/menu_ui/menu_ui.module
    @@ -201,8 +201,12 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    -      $query = \Drupal::entityQuery('menu_link_content')
    -        ->condition('link.uri', 'node/' . $node->id())
    +      $query = \Drupal::entityQuery('menu_link_content');
    +      $group = $query->orConditionGroup()
    +        ->condition('link.uri', 'entity:node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->uuid());
    +      $query->condition($group)
    
    @@ -212,8 +216,12 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    -      $query = \Drupal::entityQuery('menu_link_content')
    +      $query = \Drupal::entityQuery('menu_link_content');
    +      $group = $query->orConditionGroup()
             ->condition('link.uri', 'entity:node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->uuid());
    +      $query->condition($group)
    

    Don't we miss the case of entity:/node/$uuid?

larowlan’s picture

  1. Yep
  2. Yep
  3. Yeah, in case contrib has an entity type with a uuid and a canonical display, but no UUID route (e.g. taxonomy in core is a case of this too). Even if they have a route provider we can't be sure that the route provider provides a UUID route - this interface allows us to do that - see the hunk in EntityManager - so yeah makes 1 more important (more tests)
  4. Yep
  5. Yep
  6. Yep
acbramley’s picture

Just confirming this applies and works on 8.1.0 as well

larowlan’s picture

StatusFileSize
new6.34 KB
new28.02 KB

Fixes #53

jibran’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
@@ -83,6 +84,12 @@ public function processDefinition(&$definition, $plugin_id) {
+      $definition->setLinkTemplate('uuid', "/{$plugin_id}/{{$plugin_id}}");

Do we need an update hook to clear the cache for this?

dawehner’s picture

Just some continues review ...

  1. +++ b/core/lib/Drupal/Core/Entity/Routing/EntityUuidRouteProviderInterface.php
    @@ -0,0 +1,10 @@
    + */
    +interface EntityUuidRouteProviderInterface extends EntityRouteProviderInterface {
    

    I still haven't got the point of this interface to be honest ...

  2. +++ b/core/modules/node/src/Entity/NodeRouteProvider.php
    @@ -2,21 +2,33 @@
    +
    +    $route = (new Route("/node/{node}"))
    +      ->addDefaults([
    ...
    +      ->setRequirement('node', '^' . Uuid::VALID_PATTERN . '$')
    +      ->setRequirement('_entity_access', 'node.view');
    +    $route_collection->add('entity.node.uuid', $route);
    +
    

    I'm wondering what happens for page manager for example now that the path pattern is there now twice. Do they to check for the requirement to just override one of the specific routes or would they override both routes?

wim leers’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
    @@ -83,6 +84,12 @@ public function processDefinition(&$definition, $plugin_id) {
    +    // Add the UUID link template if applicable.
    +    $route_providers = $definition->getRouteProviderClasses();
    +    $has_uuid_route = isset($route_providers['html']) && in_array(EntityUuidRouteProviderInterface::class, class_implements($route_providers['html']), TRUE);
    +    if (!$definition->hasLinkTemplate('uuid') && $definition->hasKey('uuid') && $definition->hasViewBuilderClass() && $has_uuid_route) {
    +      $definition->setLinkTemplate('uuid', "/{$plugin_id}/{{$plugin_id}}");
    +    }
    
    +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -23,12 +24,13 @@
    + * - uuid
    

    This makes so much sense!

  2. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -83,6 +85,12 @@ public function getRoutes(EntityTypeInterface $entity_type) {
    +    // This goes before canonical so that the UUID pattern can be tested
    +    // before non-integer entity IDs.
    

    *can* or *must*?

  3. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -230,6 +238,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    +    if ($entity_type->getKey('uuid') && $entity_type->hasViewBuilderClass()) {
    

    Is this correct? What about file entities? They don't have a view builder, but can be referenced by UUID.

  4. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -230,6 +238,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    +          '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
    

    do we want this one or EntityViewController::buildTitle()? (In-place editing needs that one. I didn't even realize we also had this one.)

  5. +++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
    @@ -135,7 +135,10 @@ public function checkNodeAccess(array $tree) {
    +        ->condition('nid', $nids, 'IN')
    +        ->condition('uuid', $nids, 'IN');
    
    @@ -150,10 +153,13 @@ public function checkNodeAccess(array $tree) {
    +      $nids = (array) $query->execute();
    

    The $nids variable name no longer makes sense with this.

    Also, we need to have updated test coverage for this.

  6. +++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
    @@ -57,11 +58,19 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +        if ($entities = $storage->loadByProperties(['uuid' => $value])) {
    

    Why not use \Drupal\Core\Entity\EntityRepository::loadEntityByUuid()?

  7. +++ b/core/lib/Drupal/Core/Url.php
    @@ -336,7 +337,11 @@ public static function fromUri($uri, $options = []) {
    +      return new static("entity.$entity_type_id.uuid", [$entity_type_id => $entity_id], $options);
    

    This looks confusing.

  8. +++ b/core/modules/menu_ui/menu_ui.module
    @@ -201,8 +201,13 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    -      $query = \Drupal::entityQuery('menu_link_content')
    -        ->condition('link.uri', 'node/' . $node->id())
    +      $query = \Drupal::entityQuery('menu_link_content');
    +      $group = $query->orConditionGroup()
    +        ->condition('link.uri', 'entity:node/' . $node->id())
    +        ->condition('link.uri', 'entity:node/' . $node->uuid())
    +        ->condition('link.uri', 'internal:/node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->uuid());
    +      $query->condition($group)
    

    This looks like a straight bugfix? Nice!

    Do we have updated test coverage for this?

  9. +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php
    @@ -99,6 +100,14 @@ protected function setUpEntityTypeDefinitions($definitions = []) {
    +        // Don't allow uuid key.
    
    @@ -300,6 +309,10 @@ public function testGetRouteProviders() {
    +    // Don't allow uuid key.
    
    @@ -311,6 +324,27 @@ public function testGetRouteProviders() {
    +    // Allow uuid key.
    

    Nit: s/uuid/UUID/

dawehner’s picture

+ $has_uuid_route = isset($route_providers['html']) && in_array(EntityUuidRouteProviderInterface::class, class_implements($route_providers['html']), TRUE);

The reason why I don't understand it is that I think people should be still able to normally register routes using yml files.

larowlan’s picture

#60 they can, but they also have to add the uuid template to the entity-type annotation.

I can do taxonomy term in this patch to prove it works if that helps?

dawehner’s picture

#60 they can, but they also have to add the uuid template to the entity-type annotation.

Mh I see where you are coming from, it is still a bit weird

dawehner’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php
@@ -83,6 +84,12 @@ public function processDefinition(&$definition, $plugin_id) {
+    $has_uuid_route = isset($route_providers['html']) && in_array(EntityUuidRouteProviderInterface::class, class_implements($route_providers['html']), TRUE);

Yeah I'm not 100% sure about this one. This kinda makes the assumption we would just provide HTML routes with the 'html' key. There is no such assumption somewhere in the system so far.

larowlan’s picture

Addressing comments:

  1. #57- will add
  2. #58.1, #63 - So how about I remove the 'automagic uuid link template' feature and just add it to all the entity annotations that have a uuid route - manual or from a route provider? Then that hunk/interface can go, as can all changes to EntityManager/EntityManagerTest
  3. #59.2 - will fix
  4. #59.3 - this is the same logic for canonical paths, File entity will need to do it's own thing
  5. #59.4 - again this mimics the existing canonical path
  6. #59.5 will fix
  7. #59.6 can do - but would mean another dependency - thoughts?
  8. #59.7 any suggestions on how to address?
  9. #59.8 I think I added some, will confirm
  10. #59.9 will fix

So need some feedback from @WimLeers on #59.6 and 7 and from @dawehner on removing the magic UUID link template.

dawehner’s picture

#58.1, #63 - So how about I remove the 'automagic uuid link template' feature and just add it to all the entity annotations that have a uuid route - manual or from a route provider? Then that hunk/interface can go, as can all changes to EntityManager/EntityManagerTest

I like this idea. Less magic!

wim leers’s picture

#64:

  • #59.6: I think this is a fine dependency, because it provides the exact functionality you need. No need to duplicate the same logic.
  • #59.7: Current:
      if (Uuid::isValid($entity_id)) {
        // UUID instead of entity ID.
        return new static("entity.$entity_type_id.uuid", [$entity_type_id => $entity_id], $options);
      }
     
      return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
    

    What I think is less confusing:

      $route_name = Uuid::isValid($entity_id) ? "entity.$entity_type_id.uuid" : "entity.$entity_type_id.canonical";
      return new static($route_name, [$entity_type_id => $entity_id], $options);
    

    I think this is less confusing because it clearly shows that the route parameters are the same in both cases, it's just a different route name.

larowlan’s picture

StatusFileSize
new22.94 KB
new36.06 KB

Addressing items from #64

  1. #57- fixed
  2. #58.1, #63 - removed magic
  3. #59.2 - fixed
  4. #59.3 - nothing to do
  5. #59.4 - nothing to do
  6. #59.5 - fixed
  7. #59.6 - this changed the constructor and there were several classes extending from it that were calling the parent constructor including some in contrib (search api and workbench moderation) so changing this would be an API break, so reverted my changes
  8. #59.7 - fixed
  9. #59.8 - added
  10. #59.9 - that hunk removed as part of magic rollback
dawehner’s picture

+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -230,6 +238,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+      $route = new Route("/{$entity_type_id}/{{$entity_type_id}}");

What about using a UUID link template, in case there is one?

jibran’s picture

Other then this small feedback I think it is ready. Can you please also fix all the needs tag?

+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -230,6 +238,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+      $route = new Route("/{$entity_type_id}/{{$entity_type_id}}");
...
+          '_entity_view' => "{$entity_type_id}.full",
...
+        ->setRequirement('_entity_access', "{$entity_type_id}.view")

These double quotes are making it hard to understand. Can we add comment or use single quotes instead?

dawehner’s picture

Can we add comment or use single quotes instead?

I agree, let's go with single quotes instead ...

larowlan’s picture

#68 - @dawehner not sure what you mean there
##6 - will fix

Draft change notice is https://www.drupal.org/node/2725969

larowlan’s picture

StatusFileSize
new1.24 KB
new36.07 KB

Fixes #69

wim leers’s picture

Note this would also help make linking in formatted text fields better, because the content would actually be syncable across sites. Hence this is also related to #2292159: EditorLinkDialog should validate URLs, and autocomplete like the Link widget.

  1. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -23,6 +24,7 @@
    + * - uuid
    

    This is not listed at https://www.iana.org/assignments/link-relations/link-relations.xhtml, is that a problem?

  2. +++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
    --- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
    +++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
    

    Nit: The docblock for this class says:

    * Parameter converter for upcasting entity IDs to full objects.
    

    Needs to be updated.

  3. +++ b/core/modules/menu_ui/src/Tests/MenuTest.php
    @@ -221,6 +222,14 @@ function addCustomMenu() {
    +    // Make this menu available for node-edit forms.
    +    /* @var \Drupal\node\NodeTypeInterface $type */
    +    $type = NodeType::load('article');
    +    $node_menus = $type->getThirdPartySetting('menu_ui', 'available_menus', array('main'));
    +    $node_menus[] = $menu_name;
    +    $type->setThirdPartySetting('menu_ui', 'available_menus', $node_menus);
    +    $type->save();
    
    @@ -466,6 +475,29 @@ function doMenuTests() {
    +    $this->drupalGet($node6->url('edit-form'));
    +    $this->assertFieldByName('menu[title]', $uuid_link->label());
    +    $this->drupalPostForm(NULL, [], t('Save'));
    ...
    +    $this->drupalGet($node7->url('edit-form'));
    +    $this->assertFieldByName('menu[title]', $uuid_link->label());
    +    $this->drupalPostForm(NULL, [], t('Save'));
    

    This causes this to be testing the Node UI, rather than the Menu UI. I think it's only necessary for the assertions I mentioned. And do we really need those here?

  4. +++ b/core/modules/system/system.install
    @@ -1652,3 +1652,19 @@ function system_update_8014() {
    + * The simple presence of this update function clears cached field definitions.
    

    I don't think this comment is accurate? Looks like a c/p thing.

EDIT: forgot to say: I was very tempted to RTBC this. It's mostly because of the test that I didn't. This looks 99% ready.

larowlan’s picture

StatusFileSize
new1.15 KB
new36.41 KB

#73.1 - I thought so too, but then I looked up all of our existing link-templates and found we're already using some that don't exist - e.g. delete-form. In the existing templates, I only see duplicate and alternate as possibles but I don't think that would lead to the best DX.
In my opinion $entity->toUrl('duplicate'); isn't as obvious as $entity->toUrl('uuid'); - thoughts?
#73.2 - fixed
#73.3 - yeah I changed the logic around that form alter, so we need to test that we can also load the default value if it references a node by UUID
#73.4 - fixed

wim leers’s picture

  1. If delete-form also doesn't exist, then this is fine. I agree duplicate would be a meaningless and even confusing relation name.
  2. Yay
  3. Ok, but is this the right place to test that?
  4. Yay

So only point 3 needs further attention.

larowlan’s picture

re #75.3 will move to MenuNodeTest from MenuTest and push the ::verifyMenuLink and ::addMenuLink methods up into MenuWebTestBase, making that the new base-class for MenuNodeTest.

larowlan’s picture

StatusFileSize
new12.12 KB
new44.16 KB

Done

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

That was my only remaining concern. Thanks! :)

tstoeckler’s picture

Status: Reviewed & tested by the community » Needs work

As far as I can tell, the latest patch does not actually use the URI link template in the route provider.

larowlan’s picture

@tstoeckler can you clarify what you mean there?

larowlan’s picture

+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -83,6 +85,12 @@ public function getRoutes(EntityTypeInterface $entity_type) {
+    if ($uuid_route = $this->getUuidRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.uuid", $uuid_route);
+    }

@@ -230,6 +238,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+  protected function getUuidRoute(EntityTypeInterface $entity_type) {

@tstoeckler - this is where it is used

tstoeckler’s picture

Sorry for being so terse. What I meant was this:

In \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider::getUuidRoute():

This

+    if ($entity_type->getKey('uuid') && $entity_type->hasViewBuilderClass()) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route('/' . $entity_type_id . '/{' . $entity_type_id . '}');

should be

+    if ($entity_type->hasLinkTemplate('uuid') && $entity_type->getKey('uuid') && $entity_type->hasViewBuilderClass()) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($entity_type->getLinkTemplate('uuid'));

I.e. we should check whether the entity type specified a UUID link template and then use it to fetch the path for the route.

Hope that makes it clearer what I'm thinking.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new848 bytes
new44.15 KB

Nice one @tstoeckler - fixed

Status: Needs review » Needs work

The last submitted patch, 83: 2353611-uuid-link.83.patch, failed testing.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new885 bytes
new44.24 KB
wim leers’s picture

Status: Needs review » Reviewed & tested by the community

@tstoeckler++

dawehner’s picture

Nice!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. Looks like #82 could do with a test - no? Also don't we need to check for the case when there is no uuid link template as shown by @tstoeckler
  2. diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php
    index ecd28ca..016cca7 100644
    --- a/core/modules/menu_ui/src/Tests/MenuTest.php
    +++ b/core/modules/menu_ui/src/Tests/MenuTest.php
    @@ -8,7 +8,6 @@
     use Drupal\Core\Menu\MenuLinkInterface;
     use Drupal\Core\Url;
     use Drupal\menu_link_content\Entity\MenuLinkContent;
    -use Drupal\node\Entity\NodeType;
     use Drupal\system\Entity\Menu;
     use Drupal\node\Entity\Node;
     
    

    Unused use.

  3. +++ b/core/lib/Drupal/Core/Url.php
    @@ -336,10 +337,15 @@ public static function fromUri($uri, $options = []) {
    -      throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.");
    +      throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 or entity:node/d44a0040-2844-4cca-b0b5-20c6c96c4d8c for loading the canonical path to node entity with id 1.");
    

    Hmmm well the eg uuid doesn't really work does it.

  4. +++ b/core/modules/system/system.install
    @@ -1652,3 +1652,19 @@ function system_update_8014() {
    +function system_update_8015() {
    

    Should be system_update_8200() since we need to leave a gap in case of an emergency update function. At last that is what @catch made a recent views patch do.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new4.8 KB
new44.34 KB
dawehner’s picture

Status: Needs review » Reviewed & tested by the community

The feedback from @alexpott got addressed

alexpott’s picture

StatusFileSize
new44.38 KB
First, rewinding head to replay your work on top of it...
Applying: Init commit
Using index info to reconstruct a base tree...
M	core/lib/Drupal/Core/Entity/Entity.php
Falling back to patching base and 3-way merge...
Auto-merging core/lib/Drupal/Core/Entity/Entity.php

git diff 8.2.x HEAD core/lib/Drupal/Core/Entity/Entity.php

diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index e89ce31..403945d 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -309,6 +309,9 @@ protected function urlRouteParameters($rel) {
     if ($rel === 'revision' && $this instanceof RevisionableInterface) {
       $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
     }
+    if ($rel === 'uuid') {
+      $uri_route_parameters[$this->getEntityTypeId()] = $this->uuid();
+    }

     return $uri_route_parameters;
   }
dawehner’s picture

Merge looks perfect!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +8.2.0 release notes

Committed 85bc69d and pushed to 8.2.x. Thanks!

  • alexpott committed 85bc69d on 8.2.x
    Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers,...
bzrudi71’s picture

Status: Fixed » Needs work

I'm sorry but since commit MenuNodeTest fails on PostgreSQL for 8.2.x. Please see https://dispatcher.drupalci.org/job/php5.5_postgres9.1/

alexpott’s picture

Status: Needs work » Fixed

  • alexpott committed 895c204 on 8.2.x
    Revert "Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers...
alexpott’s picture

Status: Fixed » Needs work

After discussing with @catch decided to revert- since this introduces a postgres regression and increases the likelihood of adding more.

bzrudi71’s picture

Thanks @alexpott! Just found a minute to ran this locally and here is the PostgreSQL log:

ERROR:  invalid input syntax for integer: "d0baf2a6-48eb-4e09-919e-32bc4a69f55c" at character 391
STATEMENT:  SELECT base_table.vid AS vid, base_table.nid AS nid
	FROM 
	simpletest666842node base_table
	LEFT JOIN simpletest666842node_field_data node_field_data ON node_field_data.nid = base_table.nid
	LEFT JOIN simpletest666842node node ON node.nid = base_table.nid
	INNER JOIN simpletest666842node_field_data node_field_data_2 ON node_field_data_2.nid = base_table.nid
	WHERE ( (node_field_data.nid IN  ('d0baf2a6-48eb-4e09-919e-32bc4a69f55c')) or (LOWER(node.uuid) IN (LOWER('d0baf2a6-48eb-4e09-919e-32bc4a69f55c'))) )AND (node_field_data_2.status = '1')

PostgreSQL is a bit more strict and the IN nid condition seems obsolete ;)

daffie’s picture

Status: Needs work » Needs review
StatusFileSize
new1.13 KB
new44.59 KB

Trying to fix the PostgreSQL test bug.

Status: Needs review » Needs work

The last submitted patch, 100: 2353611-100.patch, failed testing.

daffie’s picture

Status: Needs work » Needs review
StatusFileSize
new44.58 KB
new1.79 KB
new683 bytes

Second try to fix the PostgreSQL test bug.

Status: Needs review » Needs work

The last submitted patch, 102: 2353611-102.patch, failed testing.

The last submitted patch, 91: 2353611-91.patch, failed testing.

alexpott’s picture

Discussed with @catch, @xjm, @effulgentsia, @cottser. We felt that this issue is a beta target because it has been committed once before and is only help up because postgres fails and it is an important feature for some of the API first work.

The issue summary could do with an update to explain the issue, the benefits and the solution.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new9.43 KB
new49.58 KB

This should fix the problem - also I'm not convinced the original patch actually worked because of how \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators::checkNodeAccess() was adding the access result to the tree.

alexpott’s picture

StatusFileSize
new5.04 KB
new52.5 KB

Here's an integration test for the \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators::checkNodeAccess(). I'm really sure why this exists - it is only used in \Drupal\Core\Menu\MenuParentFormSelector and it's not part of \Drupal\system\Plugin\Block\SystemMenuBlock::build() which creates the menus people see on the page.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
    @@ -150,10 +178,25 @@ public function checkNodeAccess(array $tree) {
    +      if (!empty($nids)) {
    +        $nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($nids);
    

    You would be able to skip using this load multiple by using a groupby in the query. Not sure though whether its worth to do so.

  2. +++ b/core/lib/Drupal/Core/Url.php
    @@ -325,7 +326,7 @@ public static function fromUri($uri, $options = []) {
    +   *   parse_url(). Note that {entity_id} can be both a UUID and a serial ID.
    

    Note: config entities don't have a serial ID, should we somehow point to that as well in the documentation?

  3. +++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
    @@ -338,4 +339,38 @@ function testMultilingualMenuNodeFormWidget() {
    +    /* @var \Drupal\node\NodeTypeInterface $type */
    +    $type = NodeType::load('page');
    +    // Enable the main menu for this node type..
    +    $menu_name = 'main';
    +    $type->setThirdPartySetting('menu_ui', 'available_menus', [$menu_name]);
    +    $type->save();
    +    // Test links using node/{uuid}.
    +    $node6 = $this->drupalCreateNode(array('type' => 'page'));
    +    $uuid_link = $this->addMenuLink('', '/node/' . $node6->uuid(), $menu_name);
    +    $this->verifyMenuLink($uuid_link, $node6);
    +    $this->drupalGet($node6->url('edit-form'));
    +    $this->assertFieldByName('menu[title]', $uuid_link->label());
    +    $this->drupalPostForm(NULL, [], t('Save'));
    +    \Drupal::entityManager()->getStorage('menu_link_content')->resetCache([$uuid_link->id()]);
    +    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $uuid_link */
    +    $uuid_link = MenuLinkContent::load($uuid_link->id());
    +    $this->assertEqual($uuid_link->getUrlObject(), Url::fromUri('internal:/node/' . $node6->uuid()));
    +    // Test with entity:node/{uuid}.
    +    $node7 = $this->drupalCreateNode(array('type' => 'page'));
    +    $uuid_link = $this->addMenuLink('', 'entity:node/' . $node7->uuid(), $menu_name);
    +    $this->verifyMenuLink($uuid_link, $node7);
    +    $this->drupalGet($node7->url('edit-form'));
    +    $this->assertFieldByName('menu[title]', $uuid_link->label());
    +    $this->drupalPostForm(NULL, [], t('Save'));
    +    \Drupal::entityManager()->getStorage('menu_link_content')->resetCache([$uuid_link->id()]);
    +    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $uuid_link */
    +    $uuid_link = MenuLinkContent::load($uuid_link->id());
    +    $this->assertEqual($uuid_link->getUrlObject(), Url::fromUri('entity:node/' . $node7->uuid()));
    

    Some new lines here would improve the readability quite a bit IMHO

  4. +++ b/core/modules/taxonomy/taxonomy.routing.yml
    @@ -76,6 +76,16 @@ entity.taxonomy_vocabulary.overview_form:
     
    +entity.taxonomy_term.uuid:
    +  path: '/taxonomy/term/{taxonomy_term}'
    +  defaults:
    +    _entity_view: 'taxonomy_term.full'
    +    _title: 'Taxonomy term'
    +    _title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::termTitle'
    +  requirements:
    +    _entity_access: 'taxonomy_term.view'
    +    taxonomy_term: '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'
    +
    

    What happens if views takes part in here? Don't we override this URL as well, but it doesn't work any longer?

    I think we have to expand \Drupal\views\Plugin\views\argument_validator\Entity::validateArgument to also accept UUIDs.

  5. +++ b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
    @@ -274,7 +296,7 @@ public function testCheckNodeAccess() {
    -      4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => 4))),
    +      4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.uuid', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => '7910d537-04be-450a-8200-b53f49ea6b30'))),
    

    Is it just me that it is a bit weird to remove an existing testcase instead of just adding a new one?

  6. +++ b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
    @@ -289,12 +311,32 @@ public function testCheckNodeAccess() {
    +
         $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
    +    $condition = $this->getMock(ConditionInterface::class);
         $query->expects($this->at(0))
    +      ->method('orConditionGroup')
    +      ->willReturn($condition);
    +    $condition->expects($this->at(0))
    +      ->method('condition')
    +      ->with('nid', [1, 2, 3])
    +      ->willReturn($condition);
    +    $condition->expects($this->at(1))
           ->method('condition')
    -      ->with('nid', array(1, 2, 3, 4));
    +      ->with('uuid', [3 => '7910d537-04be-450a-8200-b53f49ea6b30'])
    +      ->willReturn($condition);
         $query->expects($this->at(1))
           ->method('condition')
    +      ->with($condition);
    +    $query->expects($this->at(2))
    +      ->method('condition')
    

    This is just perfect test code ... at(N) everywhere, what else do you need

alexpott’s picture

StatusFileSize
new12.59 KB
new52.65 KB

Discussed a bit with @dawehner - so \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators::checkNodeAccess() exists for performance reasons (as it documents in its doc block). The performance fix is to not load nodes so the fix in #107 loses that benefit for UUIDs. We have three options here:

  1. To only make menu links benefit from this when they are linked by nid
  2. Make \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators::checkNodeAccess() handle both
  3. Add a new manipulator to \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators for UUIDs.

The current approach I've chosen is 2 but that's because I started from #107 - maybe it is not the best option.

alexpott’s picture

@dawehner's review in #108 still needs to be done. To be honest I don't think #108.6 is particularly fair. The existing test is full of $this->at() already and in general I'm not a fan of mocking complex queries.

dawehner’s picture

@dawehner's review in #108 still needs to be done. To be honest I don't think #108.6 is particularly fair. The existing test is full of $this->at() already and in general I'm not a fan of mocking complex queries.

We made that clear on IRC. This was me being snarky about the existing test, not about the added code.

xjm’s picture

Issue tags: -8.2.0 release notes

(Removing release notes tag; we can retag for whichever release notes whenever the change is committed.)

  • alexpott committed 85bc69d on 8.3.x
    Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers,...
  • alexpott committed 895c204 on 8.3.x
    Revert "Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers...

  • alexpott committed 85bc69d on 8.3.x
    Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers,...
  • alexpott committed 895c204 on 8.3.x
    Revert "Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers...
larowlan’s picture

Working on #108

larowlan’s picture

Assigned: larowlan » Unassigned
StatusFileSize
new9.31 KB
new57.65 KB

108.1 was handled in 109
108.2 -fixed comment
108.3 -fixed
108.4 -fixed and added a new test
108.5 -added back

dawehner’s picture

+++ b/core/modules/views/src/Plugin/views/argument_validator/Entity.php
@@ -168,8 +169,28 @@ public function validateArgument($argument) {
+      foreach ($uuids as $uuid) {
+        if ($entities = $storage->loadByProperties(['uuid' => $uuid])) {

What about using an entity query + a multiload?

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

larowlan’s picture

StatusFileSize
new1.82 KB
new57.64 KB

\Drupal\Core\Entity\EntityStorageBase::loadByProperties uses an IN query by default, so we can pass the array of UUIDs and use loadMultiple instead of looping.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

At least for me this looks great!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
    @@ -57,11 +58,19 @@ public function __construct(EntityManagerInterface $entity_manager) {
           $entity = $storage->load($value);
    +      // If there is no entity loadable by ID, try to load by UUID.
    +      if (!$entity && Uuid::isValid($value)) {
    +        if ($entities = $storage->loadByProperties(['uuid' => $value])) {
    +          $entity = reset($entities);
    +        }
    +      }
    

    We could use the ctype_digit() || is_int() trick to potentially save a query here. In fact we could add these checks to Uuid::isValid() to make it cheaper.

  2. +++ b/core/modules/menu_ui/menu_ui.module
    @@ -201,8 +201,13 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    -      $query = \Drupal::entityQuery('menu_link_content')
    -        ->condition('link.uri', 'node/' . $node->id())
    +      $query = \Drupal::entityQuery('menu_link_content');
    +      $group = $query->orConditionGroup()
    +        ->condition('link.uri', 'entity:node/' . $node->id())
    +        ->condition('link.uri', 'entity:node/' . $node->uuid())
    +        ->condition('link.uri', 'internal:/node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->uuid());
    +      $query->condition($group)
    
    @@ -212,8 +217,13 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
    -      $query = \Drupal::entityQuery('menu_link_content')
    +      $query = \Drupal::entityQuery('menu_link_content');
    +      $group = $query->orConditionGroup()
             ->condition('link.uri', 'entity:node/' . $node->id())
    +        ->condition('link.uri', 'entity:node/' . $node->uuid())
    +        ->condition('link.uri', 'internal:/node/' . $node->id())
    +        ->condition('link.uri', 'internal:/node/' . $node->uuid());
    +      $query->condition($group)
    

    Is this fixing a bug?

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new555 bytes
new58.18 KB

1 fixed
2 this is from HEAD

/**
 * Helper function to create or update a menu link for a node.
 *
 * @param \Drupal\node\NodeInterface $node
 *   Node entity.
 * @param array $values
 *   Values for the menu link.
 */
function _menu_ui_node_save(NodeInterface $node, array $values) {
  /** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */
  if (!empty($values['entity_id'])) {
    $entity = MenuLinkContent::load($values['entity_id']);
    if ($entity->isTranslatable()) {
      if (!$entity->hasTranslation($node->language()->getId())) {
        $entity = $entity->addTranslation($node->language()->getId(), $entity->toArray());
      }
      else {
        $entity = $entity->getTranslation($node->language()->getId());
      }
    }
  }
  else {
    // Create a new menu_link_content entity.
    $entity = MenuLinkContent::create(array(
      'link' => ['uri' => 'entity:node/' . $node->id()],
      'langcode' => $node->language()->getId(),
    ));
    $entity->enabled->value = 1;
  }
  $entity->title->value = trim($values['title']);
  $entity->description->value = trim($values['description']);
  $entity->menu_name->value = $values['menu_name'];
  $entity->parent->value = $values['parent'];
  $entity->weight->value = isset($values['weight']) ? $values['weight'] : 0;
  $entity->save();
}

The URI is created as 'link' => ['uri' => 'entity:node/' . $node->id()],

New tests are added here.

larowlan’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update
webchick’s picture

Version: 8.3.x-dev » 8.2.x-dev

Undoing the bot, since this is still a beta target for 8.2.x.

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
@@ -57,11 +58,19 @@ public function __construct(EntityManagerInterface $entity_manager) {
       $entity = $storage->load($value);
+      // If there is no entity loadable by ID, try to load by UUID.
+      if (!$entity && Uuid::isValid($value)) {
+        if ($entities = $storage->loadByProperties(['uuid' => $value])) {
+          $entity = reset($entities);
+        }
+      }

So now Uuid::isValid() is cheaper we can do only one query here.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new2.58 KB
new58.23 KB

Something like this...

Also I think we need to cast to string for ctype_digit because it's weird when passed something else.

alexpott’s picture

StatusFileSize
new2.73 KB
new57.94 KB

Let's not change the Uuid component unnecessarily here.

Status: Needs review » Needs work

The last submitted patch, 127: 2353611-3-127.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new1.35 KB
new58 KB

Ah I see... this loads all entities. So it also loads the string IDs of config entities too.

larowlan’s picture

Changes look good to me, thanks @alexpott

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Seems like everyone is happy.

effulgentsia’s picture

Leaving at RTBC, but a question before I'm comfortable with committing this:

With this patch, when you're on a node/{uuid} URL, then you do not get the local task tabs, such as Edit, Delete, and Revisions. Is it ok for that to be the situation in an 8.2.0 release? The IS doesn't say much about what the real use case for UUID-based URLs is, but since the patch is allowing people to add them to their menus, I'm guessing there are some sites that will want to do so and then be confused as to how to access the revision history for that node as well as all the other local tasks provided by core and contrib?

Potentially, integrating local tasks onto the UUID-based URLs could happen in follow-ups, but with the current architecture of this patch (UUID as separate routes rather than as aliases), I'm not yet clear on whether such follow-ups could be successfully implemented in patch releases, or if they'd need to be targeted for 8.3 only, and even then, how much of a pain that would end up being.

Any thoughts on this? Will we need those local tasks at some point, and if we will, then do we still think the architecture of UUID as a separate route is desirable?

effulgentsia’s picture

Version: 8.2.x-dev » 8.3.x-dev
Status: Reviewed & tested by the community » Needs work
Issue tags: -beta target

Sorry folks. I discussed this with @alexpott, who committed this originally in #93 and tagged it as a beta target in #105, and we decided that despite that original commit, #132 is enough of a concern to keep this out of 8.2.x and finish it for 8.3.x instead. Thank you to everyone who worked hard on this, it's a great feature, and I look forward to it landing in 8.3 whenever it's ready.

dawehner’s picture

One thing we could do is to provide a redirect to the other routes.

larowlan’s picture

My preference would to be to go back to the approach in #34 which was a processor.

We switched to actual routes based on @dawehner's review in #36 where we decided that the UUID regex meant a static pattern would mean there was no performance impact on a real route.

The other option is we add a local-task alter/deriver that duplicates any tasks that live on entity.{type}.canonical to also appear on entity.{type}.uuid routes - thoughts?

larowlan’s picture

Also, is there a reason why this was bunted to 8.3 without a chance/window to rectify whereas other issues that have beta target tag were given a chance to address concerns?

alexpott’s picture

@larowlan we bunted to 8.3 because the issue is a significant and rather than do a rush fix or re-architecture we decided to take our time. There is only 1 beta target left and once that is done we plan to release the next beta and after that there shouldn't be anymore new features or tasks going in to the beta.

wim leers’s picture

One thing we could do is to provide a redirect to the other routes.

IMO this makes most sense.

Otherwise you also can end up with the exact same UI, but with two different URLs. This also means that e.g. conditionally placed blocks would need to be placed at two distinct URLs. Which has ecosystem consequences.

A simple redirect fixes that. Or am I missing something?

wim leers’s picture

Issue tags: +API-First Initiative

This also has consequences for REST. Any entity that is referenced by a entity:ENTITYTYPE/UUID URI can also be resolved by REST clients. Also see #2577923: MenuLinkContent entities pointing to nodes are not deployable: LinkItem should have a "target_uuid" computed property.

larowlan’s picture

Issue tags: +Default content
acbramley’s picture

Just my 2c but I have to agree with #135, using a processor/redirect makes more sense to me rather than duplicating effort making (for example) node/{uuid} the same as node/{nid}

wim leers’s picture

Let's get this issue going again, so we can put this behind us.

Here's a recap.

  1. #132 is a valid concern and offers very convincing reasons to change direction.
  2. This patch originally used an inbound path processor (see #34). The intent is for us to have /node/[UUID] links in menus, which can be deployed across machines (unlike /node/[NID]). The inbound path processor then rewrote this to /node/[NID], which means the regular route+controller is used, and path-based matching like that used by blocks still works as expected. Finally, what's stored is UUID menu links, but they're rendered as "normal" menu links: creating a link to /node/[UUID] is still rendered as a link to /node/[NID]. The flaw: you can access /node/[UUID] and that would just work: as far as Drupal is concerned, you're at /node/[NID]. In other words: this violates the duplicate content rule: two distinct URLs serve identical content.
  3. Then this patch shifted towards using actual routes (#36 through #129). The flaw: because each entity now has a UUID route too, local tasks, block placement, etc all don't work as expected anymore. Each entity now effectively has two URLs. This is even worse.
  4. #134 proposed to solve this via redirects, and I agreed in #138. The UUID URL would redirect to the non-UUID URL. It has the potential to deliver the best of both prior approaches: A) storing links to /node/[UUID], but rendering as /node/[NID], B) those who access /node/[UUID] directly are redirected to /node/[NID] and C) hence block placement etc still work as expected.

I first wanted to agree with larowlan in #135, and the patch in #34 even still applies cleanly (!!!), but I did discover this one big flaw listed above. Hence the redirect approach seems the best candidate.

Crell’s picture

To clarify #142: You're suggesting:

1) When generating outbound URLs, we still convert node/abc123 to just node/4, so we never OUTPUT uuid-based URLs.

2) If someone sends a uuid-based request anyway, we 301 redirect to node/4 (or its alias).

If so, that sounds like a good plan to me.

larowlan’s picture

Yeah, I think we're stuck with a redirect

wim leers’s picture

  • #143: yep, exactly!
  • #144: glad to see you're on board!

@larowlan, do you have the time+interest to reroll this? I would provide reviews.

larowlan’s picture

interest √
time ?

this time of year lots of stuff going on outside, will see what I can do

acbramley’s picture

StatusFileSize
new57.91 KB

Rerolling #129 to latest 8.2.x as the system update hooks conflicted.

One thing to note from the difference in #129 to #34 that may be of interest: I use a node/uuid path for the site front page so that I can have a homepage node to control the content, with #129 blocks and things that are set to display on work fine but not with #34

wim leers’s picture

#147: can you please provide an interdiff? https://www.drupal.org/documentation/git/interdiff

acbramley’s picture

StatusFileSize
new593 bytes

Sorry, here you go

acbramley’s picture

StatusFileSize
new57.91 KB
new823 bytes

Another update hook collision :(

jibran’s picture

Status: Needs work » Needs review
wim leers’s picture

StatusFileSize
new57.91 KB

Reuploading the patch in #150, because it was never tested by testbot.

Status: Needs review » Needs work

The last submitted patch, 152: 2353611-150.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.01 KB
new60.2 KB
00:00:46.346 PHP Fatal error:  Cannot redeclare system_update_8203() (previously declared in /var/www/html/core/modules/system/system.install:1789) in /var/www/html/core/modules/system/system.install on line 1803

Renaming system_update_8203() to system_update_8300().

Status: Needs review » Needs work

The last submitted patch, 154: 2353611-154.patch, failed testing.

wim leers’s picture

StatusFileSize
new60.11 KB
new623 bytes

#2842480: system_update_8203() is named incorrectly just landed, so now #154 must be done again, now moving from system_update_8300 to system_update_8301.

Doing that first, then addressing the failure.

wim leers’s picture

StatusFileSize
new60.78 KB
new711 bytes

The failure in #154 is because #2585899: HtmlResponseAttachmentsProcessor::processHtmlHeadLink produces invalid HTTP Link header landed since the last green test run, and it added a new test that we now also need to update.

wim leers’s picture

Status: Needs work » Needs review
wim leers’s picture

StatusFileSize
new62.92 KB
new8.67 KB

So, in #142, I tried to get this back on the rails.

In #143, Crell summarized it as follows:

To clarify #142: You're suggesting:

1) When generating outbound URLs, we still convert node/abc123 to just node/4, so we never OUTPUT uuid-based URLs.

2) If someone sends a uuid-based request anyway, we 301 redirect to node/4 (or its alias).

If so, that sounds like a good plan to me.

But as the fail in #154 and the fix in #157 are proving, we are outputting a UUID-based URL: in the Link headers. Of course, that UUID-based URL would respond with a redirect.

… except that the patch I've been rerolling to get to green is the WRONG PATCH, because @acbramley rerolled #129 in #147, rather than implementing what we discussed & agreed in #142 (me) + #143 (Crell) + #144 (larowlan).

Gaahhh!!!! I should've looked at the patches since #142 more closely before spending any time on it :(


So, I'm adding a \Drupal\Core\Entity\Controller\EntityViewController::redirectUUidToCanonical method, and am making the necessary corresponding rout e modifications to make this happen. That redirectUUidToCanonical() method is modeled after \Drupal\comment\Controller\CommentController::redirectNode().

It seems this is unfortunately not yet passing because Views is apparently still incorrectly overriding the entity.taxonomy_term.canonical route with its own controller. Or at least, that's what I conclude when I do the debugging of the exception that I'm getting, which looks like this:

Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("entity") to generate a URL for route "entity.taxonomy_term.canonical". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 180 of core/lib/Drupal/Core/Routing/UrlGenerator.php).

I'll let somebody else take it from here.

Status: Needs review » Needs work

The last submitted patch, 159: 2353611-158.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new561 bytes
new62.93 KB

Oops, I forgot a hunk.

This will still fail for the reasons I outlined in my previous comment.

Status: Needs review » Needs work

The last submitted patch, 161: 2353611-161.patch, failed testing.

  • alexpott committed 85bc69d on 8.4.x
    Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers,...
  • alexpott committed 895c204 on 8.4.x
    Revert "Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers...

  • alexpott committed 85bc69d on 8.4.x
    Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers,...
  • alexpott committed 895c204 on 8.4.x
    Revert "Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers...

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

acbramley’s picture

@Wim Leers sorry, what did I do wrong?? #147 was a straight reroll of the previous patch to fix the update hook issue. The reason I didn't reroll the older patch with the newly agreed approach is because I had issues with it which are explained in #147...

wim leers’s picture

@acbramley: no need to apologize, this is a super super super confusing issue! I didn't mean to blame you at all, sorry if that's how it came across. #142 basically said we need a hybrid of the different approaches we had had up until that point.

#159 then got that hybrid going.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Entity.php
    @@ -315,6 +315,9 @@ protected function urlRouteParameters($rel) {
    +    if ($rel === 'uuid') {
    

    This would not be flexible enough for jsonapi. I guess we would need to load the route and check somehow whether something needs a uuid. Not sure how to detect that though.

  2. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -235,6 +244,31 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    +        ->setRequirement('_access', 'TRUE')
    

    I'm wondering whether we should copy over the access checking of the normal entity somehow

wim leers’s picture

#168.1: Perhaps we should not expand Drupal/Core/ParamConverter/EntityConverter's capabilities, but instead add a new parameter converter. So that we have {node} and {node_uuid}, instead of just {node} that accepts both ID and UUID. Then it'd be trivial to also make this work for JSON API and other UUID-based link templates.

damienmckenna’s picture

@Wim Leers: Is that a decision the Entity subsystem maintainers should weigh in on?

dawehner’s picture

#168.1: Perhaps we should not expand Drupal/Core/ParamConverter/EntityConverter's capabilities, but instead add a new parameter converter. So that we have {node} and {node_uuid},

Not sure how you think this should work for the original problem this issue tried to solve: Linking to /node/$uuid should be possible. Therefore it has to be the same param converter. In the usecase of JSONAPI it would work to have two separate converters, but there is still no easy way to link to it.

wim leers’s picture

Therefore it has to be the same param converter.

Why?

We could add a entity.<entity type ID.uuid route in \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider, because it's already $rel == 'uuid' anyway.

dawehner’s picture

We could add a entity.

Note: We do that already. When we though use a different parameter name, like node_uuid, stuff like local tasks won't work anymore.

wim leers’s picture

When we though use a different parameter name, like node_uuid, stuff like local tasks won't work anymore.

But that's exactly what we want. We want /node/<UUID> to redirect to /node/<ID>. See #142.

gambry’s picture

I came across this issue while a client requires content URLs not to be "guess-able". So disallowing the use of /node/<ID> as ID is incremental and users can just decrease/increase its value. And the easiest solution seems to be using /node/<UUID>.

But if I integrate the solution described from #142.4, so with the redirect, this won't work anymore.

I understand this issue original value is to be able to deploy links to content between environments without breaking them, but I can see obfuscating content paths as a good value too.

From #142.2:

This patch originally used an inbound path processor (see #34). The intent is for us to have /node/[UUID] links in menus, which can be deployed across machines (unlike /node/[NID]). The inbound path processor then rewrote this to /node/[NID], which means the regular route+controller is used, and path-based matching like that used by blocks still works as expected. Finally, what's stored is UUID menu links, but they're rendered as "normal" menu links: creating a link to /node/[UUID] is still rendered as a link to /node/[NID]. The flaw: you can access /node/[UUID] and that would just work: as far as Drupal is concerned, you're at /node/[NID]. In other words: this violates the duplicate content rule: two distinct URLs serve identical content.

The flaw exists anyway if you use multiple path aliases or whatever shows the same content. The fix for it is simply using the canonical URL meta tag which AFAIK is already on node and comments by default.

Ignore my comment if you don't think obfuscating the path is an additional value of using UUID instead of ID on paths, but generally I don't see why #142.2 was a bad approach.

geek-merlin’s picture

#175: So you say the canonical-url metatag makes the redirect obsolete?
I'd say good news, as that redirect never felt really good imho.

berdir’s picture

#175: Even if it would not be a redirect it wouldn't make Drupal actually *use* that route or prevent anymore from using node/ID. That is not the goal here, redirect or not.

IMHO, if your goal is to prevent access to node/ID, then this is not the right approach to do that. All you need for that are aliases (You could even make aliases the contain the UUID, that's trivial. Or anything else.) and a early request subscriber that denies access if the original URL before the alias resolver is node/ID.

gambry’s picture

@Berdir , yeah I probably didn't make myself totally clear.
I found this issue while wondering 'Can I resolve an entity using its UUID?' and the work done so far solves the problem perfectly up to when it switched to redirect /node/<UUID> to /node/<NID>.

Redirecting UUID path --> NID path will make impossible to use just plain /node/<UUID> URLs.
So 'Can I resolve an entity using its UUID?' is answered by 'Kind of. Visiting /node/UUID will redirect to /node/NID and that will resolve your entity'.

My real question was if we do need to redirect. #142.2 refers to a "In other words: this violates the duplicate content rule: two distinct URLs serve identical content." flaw which is a problem taken care by the canonical metatag already existing on nodes.
Also @larowlan on #135 suggested to go back to original - without redirect - solution in #34.

[[P.S.: I personally solved my issue extending EntityConverter as per suggested by patch in here, but I haven't though about aliases. Thanks for the hint!]]

grimreaper’s picture

StatusFileSize
new63.36 KB

Hello,

Here is a reroll the patch from comment #161 against branch 8.3.x. I was not able to make an interdiff because patch from comment #161 did not apply anymore.

If I understood the comments it is this patch that needs to pass. If someone can confirm, thanks.

I have changed arrays into short array syntax and tried to update some code in the tests that

I had a problem updating come code in the tests because of properties that changed. I have commented using // the parts I was not able to port.

I have not tested the patch yet.

grimreaper’s picture

After applying the patch, if I try to go to node/[UUID], I am redirected to node/[NID]. That is ok.

But the UUID does not seems to be stored in case of a link field, it is still the ID.

And I have the following error when displaying a taxonomy term field value:

Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("entity") to generate a URL for route "entity.taxonomy_term.canonical". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 180 of core/lib/Drupal/Core/Routing/UrlGenerator.php).
Drupal\Core\Routing\UrlGenerator->getInternalPathFromRoute('entity.taxonomy_term.canonical', Object, Array, Array) (Line: 291)
Drupal\Core\Routing\UrlGenerator->generateFromRoute('entity.taxonomy_term.canonical', Array, Array, 1) (Line: 105)
Drupal\Core\Render\MetadataBubblingUrlGenerator->generateFromRoute('entity.taxonomy_term.canonical', Array, Array, 1) (Line: 759)
Drupal\Core\Url->toString(1) (Line: 167)
Drupal\Core\Utility\LinkGenerator->generate('tag', Object) (Line: 94)
Drupal\Core\Render\Element\Link::preRenderLink(Array)
call_user_func(Array, Array) (Line: 376)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 195)
Drupal\Core\Render\Renderer->render(Array) (Line: 490)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 76)
__TwigTemplate_a6591983c7b2818f783039d045606bc65df838e04dad4892ffdd0917b7d12d79->doDisplay(Array, Array) (Line: 379)
Twig_Template->displayWithErrorHandling(Array, Array) (Line: 347)
Twig_Template->display(Array) (Line: 358)
Twig_Template->render(Array) (Line: 64)
twig_render_template('core/themes/bartik/templates/field--node--field-tags.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('field', Array) (Line: 435)
Drupal\Core\Render\Renderer->doRender(Array) (Line: 448)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 195)
Drupal\Core\Render\Renderer->render(Array) (Line: 490)
Drupal\Core\Template\TwigExtension->escapeFilter(Object, Array, 'html', NULL, 1) (Line: 114)
__TwigTemplate_a624ba251fc4b3c6b5feacfb6a9c332a974950caa272a7ee83a047a42ffdc8b3->doDisplay(Array, Array) (Line: 379)
Twig_Template->displayWithErrorHandling(Array, Array) (Line: 347)
Twig_Template->display(Array) (Line: 358)
Twig_Template->render(Array) (Line: 64)
twig_render_template('core/themes/bartik/templates/node.html.twig', Array) (Line: 384)
Drupal\Core\Theme\ThemeManager->render('node', Array) (Line: 435)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 195)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 226)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 574)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 227)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 117)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.view', Object) (Line: 149)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 64)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 656)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
wim leers’s picture

#175: security through obscurity is never a good idea. If your client requirement is like that, then I suggest instead implementing Entity Access to disallow access. And if a 403 is already divulging information, then map 403s to 404s. Done.
Or… what @Berdir described in #177.

#178: the "duplicate content" I wrote was not from an SEO POV, but from a Drupal POV: we simply shouldn't have two routes serving the exact same content. This has consequences beyond SEO. An example: a block is made visible only for /node/5… but then if you access it via /node/<UUID for node 5>, then you don't see the block. Clearly dangerous.

#180: I already explained that failure at the bottom of #159: this is a long-standing bug in Views.

michaellander’s picture

So the JSONAPI module currently requires the bundle to request a resource(i.e. /jsonapi/node/article/), and I'm curious if that should be considered here. For example, If I'm moving some link field data between two sites and would like to lookup the related entity from the link, it would be great to have all the information needed for the subsequent request. This probably opens a whole other can of worms...

grimreaper’s picture

Hello,

@Wim Leers: I tried the patch from comment 179 and #2449143-123: REST views specify HTML as a possible request format, so if there is a "regular" HTML view on the same path, it will serve JSON after applying it manually because it does not apply on Drupal 8.3.2.

And I still have the Views error.

I am trying to use UUID in link field in JSON API without having to rely on this issue. I wrote a patch to alter the display of link fields #2883437: Transform link field value to use UUID but now I need to handle the deserialization.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

reshma_n’s picture

StatusFileSize
new85.3 KB

Rerolling #157 against latest 8.3.x. The reason I didn't reroll against the newer patch (hybrid approach) is that it had issues.

wim leers’s picture

Priority: Normal » Major

I think this ought to be major given its impact/value.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

I'm wondering whether we could split up this quite big patch into two bits:

  1. The route / entity API changes
  2. The linking changes (menu items ...)

This way we could quickly focus on the first bit, which should be easier to get done. Other issues like #2878463: [PP-1] Define a JSON API link relation for entities could leverage it.

wim leers’s picture

#189++, that would be lovely.

wim leers’s picture

mglaman’s picture

For #189 would that then just be the changes

  • diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
  • diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
  • diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
  • (? maybe) diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php

Would we also target the Node route provider and UserRouteProvider.php for first half of the patch? Or first half is generic implementation, last half is per-entity and other menu support.

Or everything in patch sans things relating to DefaultMenuLinkTreeManipulatorsTest

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new51.79 KB
new40.99 KB

Rerolling patch from #183 against the latest 8.6.x

Status: Needs review » Needs work

The last submitted patch, 193: 2353611-193.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new53.57 KB

Re-adding missing parts

Status: Needs review » Needs work

The last submitted patch, 195: 2353611-195.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new1.07 KB
new53.39 KB

Status: Needs review » Needs work

The last submitted patch, 197: 2353611-197.patch, failed testing. View results

pwolanin’s picture

I found the draft change notice and disappointed this is not actually working yet.

I think dawehner is right - this patch has way too much scope. The views, menu link, and other support seem like they are not essential.

Also the issue summary is not clear as to whether there is a compelling reason we have to use the same path as the canonical path.

It would be a lot easier to make this work if the path was like node/{node}/uuid or uuid/node/{node} or whatever variant of that makes sense.

geek-merlin’s picture

> It would be a lot easier to make this work if the path was like node/{node}/uuid or uuid/node/{node} or whatever variant of that makes sense.

Strong +1 for that. So it's clear from the route name what is requested.

daffie’s picture

If all UUIDs are unique, would it not be better to use a generic path like: uuid/{uuid}?

mglaman’s picture

If all UUIDs are unique, would it not be better to use a generic path like: uuid/{uuid}?

You still need to know the type, so you know which table.

I was really hoping we could get core to use node/{uuid} so we can move to avoiding database specific identifiers in entity canonical routes.

wim leers’s picture

@pwolanin If you put your shoulders under this one, I'm sure that we could get this done very soon :) Especially look at the recap I wrote in #142. I ran out of steam after #161/have had higher priorities to deal with.

I was really hoping we could get core to use node/{uuid} so we can move to avoiding database specific identifiers in entity canonical routes.

I don't understand this, can you clarify?

mglaman’s picture

Sorry, I'll clarify.

I was hoping we could easily use /node/52 and /node/1111-111-111-122 to retrieve the same node. Instead of what is being proposed to have a specific route for retrieving via UUID. See #199's proposal for a UUID route.

wim leers’s picture

Right. I think I agree. See #174 and #142.

@dawehner proposed in #189 to split the patch in routing vs menu linking parts. The routing portion of the patch actually already does what you say, @mglaman, and matches what's in #174/#142. It's only #199 that's proposing a new path pattern for the route.

geek-merlin’s picture

> I was hoping we could easily use /node/52 and /node/1111-111-111-122 to retrieve the same node.

Just realizing: For this we would need a release note like:
"If you use entities with string IDs, as a site architect you must ensure that the ID will never clash with a UUID.
This restriction is mitigated by the fact that such a clash is quite unlikely, and aggravated by the fact that such a clash will be extremely unexpected and hard to debug."

geek-merlin’s picture

Also note that loading an entity with string IDs will take 2 queries in the general case, if the string ID can be an ID or UUID.

pwolanin’s picture

I don't understand the use case for loading at the same path - are you somehow not aware of what kind of id (int vs uuid) that you have?

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new30.6 KB

Here's the routing portion of patch (menu and view parts removed). UUID paths still match canonical ones, although I too think there should be different pattern (between 2 suggestions in #109 I'd choose `/uuid/{entity_type}/{uuid}`

Is there an agreement on this?

Status: Needs review » Needs work

The last submitted patch, 209: 2353611-209.patch, failed testing. View results

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new30.65 KB

Oops, forgot to add `use` in UseRouteProvider.php

Status: Needs review » Needs work

The last submitted patch, 211: 2353611-211.patch, failed testing. View results

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new21.3 KB

Remove also link templates (main reason for failing tests)

Status: Needs review » Needs work

The last submitted patch, 213: 2353611-213.patch, failed testing. View results

valthebald’s picture

Status: Needs work » Needs review
StatusFileSize
new18.29 KB

Remove also menu link access test change, as this part will be spun off

pwolanin’s picture

So looks like this is still using the same paths in the latest patch?

valthebald’s picture

@pwolanin: I wanted to know the reasoning to use the same path. If there is no one, I can switch uuid paths to have different pattern

valthebald’s picture

StatusFileSize
new1.59 KB
new18.31 KB

Changing UUID paths to pattern `/uuid/{entity_type}/{entity}`

valthebald’s picture

Issue summary: View changes

Update issue description

wim leers’s picture

Please read the prior discussion on the issue. I even pointed to the specifically relevant comments in #205.

Quoting #142.3:

The flaw: because each entity now has a UUID route too, local tasks, block placement, etc all don't work as expected anymore. Each entity now effectively has two URLs. This is even worse.

That's why you don't want separate paths/URLs.

valthebald’s picture

@Wim Leers: I thought that this flaw is addressed by redirecting UUID path to canonical one, as you wrote in #142 .4

pwolanin’s picture

Is the end goal to redirect to the canonical path or serve the entity whether an ID or UUID is provided?

@Wim Leers - it seems like this is going to break things like path aliases either way?

geek-merlin’s picture

It is like stated in #221 iand #142:

The UUID URL would redirect to the non-UUID URL. It has the potential to deliver the best of both prior approaches: A) storing links to /node/[UUID], but rendering as /node/[NID], B) those who access /node/[UUID] directly are redirected to /node/[NID] and C) hence block placement etc still work as expected.

We are just to sort out what the actual uuid path should be.
a) /[ENTITY_TYPE_ID]/[UUID] might lead to clashes, so we might use b) /uuid/[ENTITY_TYPE_ID]/[UUID] of even c) /uuid/[UUID] (as it's a UUID).

I looked in the D7 uuid module, it uses c) to redirect to the canonical route (like we want it here), but b) may have advantages too.

acbramley’s picture

c) /uuid/[UUID] (as it's a UUID).

We can't do that as we need to know the entity_type when looking up by uuid.

vijaycs85’s picture

+1 to keep node/[uuid] as this could happen when we get the circular dependency on the default content or replication, we can fall back to uuid

valthebald’s picture

https://www.drupal.org/files/issues/2018-05-06/2353611-215.patch is keeping node/{uuid} pattern (still applies and passes)

mahtab_alam’s picture

StatusFileSize
new18.24 KB
valthebald’s picture

@mahtab_alam can you provide an interdiff please? How your patch is different from previous?

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

valthebald’s picture

Issue summary: View changes
ricovandevin’s picture

We have tried the patch from #215 but it seems that the "node" parameter in de request does not upcasted to the actual Node object when doing a request to /node/[UUID]. The parameter propagates as a string (being the UUID) and that breaks the entity access check. The check results in a "neutral" and since there is no other access check (in our case) that results in a "allowed" we get a access denied in the end.

Update the difference described below is actually originating from custom code in the project.

When I compare the Route objects as stored in the router table I notice that the converter is present for the canonical route but not for the uuid route. In fact the complete parameters section is missing for the uuid route.

Canonical:
C:31:"Symfony\Component\Routing\Route":1164:{a:9:{s:4:"path";s:12:"/node/{node}";s:4:"host";s:0:"";s:8:"defaults";a:2:{s:11:"_controller";s:58:"Drupal\kankernl_library\Controller\LibraryController::view";s:15:"_title_callback";s:49:"\Drupal\node\Controller\NodeViewController::title";}s:12:"requirements";a:2:{s:4:"node";s:3:"\d+";s:14:"_entity_access";s:9:"node.view";}s:7:"options";a:3:{s:14:"compiler_class";s:34:"\Drupal\Core\Routing\RouteCompiler";s:10:"parameters";a:1:{s:4:"node";a:2:{s:4:"type";s:11:"entity:node";s:9:"converter";s:21:"paramconverter.entity";}}s:14:"_access_checks";a:1:{i:0;s:19:"access_check.entity";}}s:7:"schemes";a:0:{}s:7:"methods";a:2:{i:0;s:3:"GET";i:1;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";C:33:"Drupal\Core\Routing\CompiledRoute":425:{a:11:{s:4:"vars";a:1:{i:0;s:4:"node";}s:11:"path_prefix";s:0:"";s:10:"path_regex";s:25:"#^/node/(?P<node>\d+)$#sD";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:4:"node";}i:1;a:2:{i:0;s:4:"text";i:1;s:5:"/node";}}s:9:"path_vars";a:1:{i:0;s:4:"node";}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:2;s:14:"patternOutline";s:7:"/node/%";s:8:"numParts";i:2;}}}}

Uuid:
C:31:"Symfony\Component\Routing\Route":1101:{a:9:{s:4:"path";s:12:"/node/{node}";s:4:"host";s:0:"";s:8:"defaults";a:1:{s:11:"_controller";s:75:"Drupal\Core\Entity\Controller\EntityViewController::redirectUUidToCanonical";}s:12:"requirements";a:2:{s:4:"node";s:41:"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}";s:14:"_entity_access";s:9:"node.view";}s:7:"options";a:2:{s:14:"compiler_class";s:34:"\Drupal\Core\Routing\RouteCompiler";s:14:"_access_checks";a:1:{i:0;s:19:"access_check.entity";}}s:7:"schemes";a:0:{}s:7:"methods";a:2:{i:0;s:3:"GET";i:1;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";C:33:"Drupal\Core\Routing\CompiledRoute":502:{a:11:{s:4:"vars";a:1:{i:0;s:4:"node";}s:11:"path_prefix";s:0:"";s:10:"path_regex";s:63:"#^/node/(?P<node>[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})$#sD";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:41:"[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}";i:3;s:4:"node";}i:1;a:2:{i:0;s:4:"text";i:1;s:5:"/node";}}s:9:"path_vars";a:1:{i:0;s:4:"node";}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:2;s:14:"patternOutline";s:7:"/node/%";s:8:"numParts";i:2;}}}}

ricovandevin’s picture

The problem seems to be a mismatch in parameter names. The entity.node.uuid defines a parameter with the name node. The EntityViewController::redirectUUidToCanonical() method expects a parameter with the name $entity. This causes EntityResolverManager::setParametersFromReflection() to look for a parameter with the name entity in the route. Since the route does not provide a default for _entity_view or _entity_form either the node parameter can not be upcasted for the entity.node.uuid route.

When I add a redirectUUidToCanonical() method to the NodeViewController class with a parameter $node that just returns the result of the same method on the parent class (being EntityViewController) and I change the controller name for the entity.node.uuid route in the NodeRouteProvider to NodeViewController all works well.

ricovandevin’s picture

StatusFileSize
new18.9 KB
new1.46 KB

Updated patch based on #215 attached.

gabesullice’s picture

Status: Needs review » Needs work

Good catch @ricovandevin!

+++ b/core/modules/node/src/Entity/NodeRouteProvider.php
@@ -21,7 +21,7 @@
-        '_controller' => EntityViewController::class . '::redirectUUidToCanonical',
+        '_controller' => NodeViewController::class . '::redirectUUidToCanonical',

+++ b/core/modules/node/src/Controller/NodeViewController.php
@@ -109,4 +109,11 @@ public function title(EntityInterface $node) {
+  public function redirectUUidToCanonical(EntityInterface $node) {

s/UUid/Uuid/g

manuel garcia’s picture

Status: Needs work » Needs review
StatusFileSize
new4.25 KB
new18.92 KB
claudiu.cristea’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
    @@ -121,4 +123,26 @@ public function viewRevision(EntityInterface $_entity_revision, $view_mode = 'fu
    +  public function redirectUuidToCanonical(EntityInterface $entity) {
    

    I think we should use different controllers for different routes. I'm not a fan of dropping everything in a single controller. Why loading a lot of useless methods for a given route? EDIT: Sorry, this is the standard entity view controller. Ignore my comment.

  2. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -238,6 +246,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    +    if ($entity_type->getKey('uuid') && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('uuid')) {
    

    Probably $entity_type->hasKey('uuid') is more correct semantically?

  3. +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
    @@ -238,6 +246,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    +        ->setRequirement($entity_type_id, '^' . Uuid::VALID_PATTERN . '$');
    
    +++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
    @@ -100,7 +101,15 @@ public function convert($value, $definition, $name, array $defaults) {
    +    if (!(is_int($value) || ctype_digit((string) $value)) && Uuid::isValid($value)) {
    
    +++ b/core/lib/Drupal/Core/Url.php
    @@ -341,10 +343,14 @@ public static function fromUri($uri, $options = []) {
    +    if (Uuid::isValid($entity_id)) {
    
    +++ b/core/modules/node/src/Entity/NodeRouteProvider.php
    @@ -17,6 +19,15 @@ class NodeRouteProvider implements EntityRouteProviderInterface {
    +      ->setRequirement('node', '^' . Uuid::VALID_PATTERN . '$')
    
    +++ b/core/modules/taxonomy/taxonomy.routing.yml
    @@ -28,6 +28,14 @@ entity.taxonomy_term.delete_form:
    +    taxonomy_term: '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'
    
    +++ b/core/modules/user/src/Entity/UserRouteProvider.php
    @@ -17,6 +19,16 @@ class UserRouteProvider implements EntityRouteProviderInterface {
    +      ->setRequirement('user', '^' . Uuid::VALID_PATTERN . '$')
    

    Well, I'm really concerned about this because smells like an assumption. I'm working on a project where the UUID is not following this pattern. Our UUIDs are in fact URIs, which are universally unique, at least in *this* internet. Note that the UUID generation is a service that can be swapped. A project could imagine its own UUID pattern. For this reason I'm proposing that we let the UUID generator to decide if the UUID is valid. This means that we add a new method \Drupal\Component\Uuid\UuidInterface::isValid() (or similar).

    EDIT: An alternative would be to switch to a different route path pattern.

  4. +++ b/core/modules/node/src/Controller/NodeViewController.php
    @@ -109,4 +109,11 @@ public function title(EntityInterface $node) {
    +  public function redirectUuidToCanonical(EntityInterface $node) {
    

    ???? :)

  5. +++ b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml
    @@ -1,3 +1,11 @@
    +    taxonomy_term: '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'
    

    s/taxonomy_term/entity_test

Setting to NR especially to discuss 3.

vijaycs85’s picture

  1. +++ b/core/modules/node/src/Controller/NodeViewController.php
    @@ -109,4 +109,11 @@ public function title(EntityInterface $node) {
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function redirectUuidToCanonical(EntityInterface $node) {
    +    return parent::redirectUuidToCanonical($node);
    +  }
    +
    

    Why do we need this method anyway? not having it eventually call the parent's redirectUuidToCanonical()

  2. Regarding #236.3, just discussed with @dawehner and suggested we could add a follow up as it does not affect the default behavior.
claudiu.cristea’s picture

Regarding #236.3, just discussed with @dawehner and suggested we could add a follow up as it does not affect the default behavior

Why not the the other way around? Fix first the UUID validation in a new issue and postpone this on that.

Marishka_’s picture

Testing the patch from #235, we're seeing buggy behaviour when referencing the homepage by uuid.
We have a node with alias /home which is set as the site homepage by uuid link (the site front page is set to /node/d411fb13-5266-4e6e-8460-1a6bc917f9e6 ).

Going to https://www.mysite.com/ redirects to http://www.mysite.com/home which breaks because of the redirect to http. (Yes this can be worked around in other ways, but I don't believe it's desirable behaviour).

What I'd expect is that when the homepage is set via uuid link, https://www.mysite.com/ should load https://www.mysite.com/ (no URL change) showing the homepage specified.

eelkeblok’s picture

This patch also seems to cause any URL that should normally generate a 404 to redirect to the front page (which is probably related to the behaviour reported in #239).

Edit: To clarify, the observed behaviour is connected to the fact I tried using this patch to set the 404 page based on a UUID URL. I do think this is an important use case for this functionality, though.

eelkeblok’s picture

StatusFileSize
new30.57 KB
new11.67 KB

So, AFAICT, the approach suggested in #142 was actually never implemented, i.e. the latest patch still "only" does the redirecting. When Drupal decides it needs to delegate the 404 handling to a custom route, it executes that route which then results in a redirect (I think it is actually a little less clear cut than that, because I ended up with a 302... 😕). This would probably also at least partially explain #239.

I did a quick and dirty merge of the changes from #34, excluding the changes in menu_ui (i.e. the interdiff is basically #34 rerolled). The project I am testing this on also includes Redirect module, but I am seeing the exact behaviour I would like now:

* Configuring the 404 page with a node/{uuid} URL keeps the URL in the address bar, gives a 404 status code, and actually displays the page I wanted (i.e. just as if I would have used the regular nid-url).
* Directly visiting the /node/{uuid} URL redirects me to the path alias. At least one part of this is down to redirect module, undoubtedly. I'm not sure if the redirect code from this patch still plays a part in that.

I would like to explore a few things:

  • What happens without Redirect module?
  • Is it really true that the redirect is needed to make stuff like URL-based block placement work? How is this different from the system path and the alias serving up the exact same content?
  • Does it work for the frontpage too?

#236 and #237 still need to be processed.

eelkeblok’s picture

StatusFileSize
new30.6 KB

This is a reroll against 8.7.x.

eelkeblok’s picture

Issue summary: View changes

I updated the issue summary with the use cases I can think of spelled out.

eelkeblok’s picture

Issue summary: View changes
eelkeblok’s picture

Issue summary: View changes
eelkeblok’s picture

I've done some testing with the combined patch. It looks like the redirecting is completely masked by the path processor. Which is actually fine, I think, because Drupal core correctly sets a canonical meta tag, which is set to the alias when one exists, or to the numeric node ID when it doesn't. This is also true when viewing the node through the UUID URL.

Going through the "acceptance criteria" I added to the summary yesterday:

Allow a UUID-based URL to be used in menus, end-user-facing links should use a path alias when available

Check.

  • When adding a menu link to a node without an alias (using the UUID URL, obviously), the URL shown in the link is the numeric node URL.
  • When adding a menu link to a node with an alias (again, using the UUID URL, obviously), the URL shown in the link is the alias.

Allow a UUID-based URL to be used for front page, 403 and 404 pages, without strange redirection by-effects

Check. (I did not test the 403 page, but I have no reason to believe that is any different from the 404).

Allow a UUID-based URL to be used as in the page condition for e.g. block-placement. It should not make a difference if the alias-, numeric ID- or UUID-based URL is used, the block should be shown regardless of the URL used to access the content

Umm... Not exactly. However, when you use either the alias or the numeric ID as the condition, the condition does trigger when accessing the page through the UUID, so this might be something that could be done in a follow-up issue.

The "duplicate content" situation is not made worse then it is, i.e. search engines should be guided to a canonical URL somehow, either by redirects (for aliases and traditional numeric URLs this is handled by Redirect module in contrib) or a canonical metatag.

See above. Core already properly sets a canonical meta tag. Ideally, I do believe a site should issue redirects to make sure content is every accessed through a single URL, but from an SEO point of view, I don't think this extra URL based on the UUID actually makes the situation any worse; search engines will be directed to the "one true" URL using the meta tag (as well as them all resolving to that same "one true" URL when rendering in e.g. a menu). Redirect module can still handle the actual redirecting side of things, as it does now (and if we feel strongly enough, maybe the redirecting can be added to core, but I don't think it needs to be in the scope of this change).

So, that leaves the following tasks:

* Remove code to handle redirection.
* Process remaining feedback from #236 and #237.

eelkeblok’s picture

StatusFileSize
new14.41 KB
new16.88 KB

So let's see if we still have green tests. I did process the remaining tasks from my previous comment. The points from 236 and 237 were rendered obsolete by the removal of the redirects.

However, while going through the code, I did realise something. One, the node entity did not actually have a uuid link template, so I guess that could not actually work..? Also, it looks like we have some competing assumptions. On the one hand, links were assumed to be {entity-type}/{uuid}, on the other hand, the canonical link template was being used. This is often the same, but not always (e.g. taxonomy term). Because the route for the redirecting is completely gone now, there are no competing assumptions any more, but we still do have an inconsistency due to it; the path processor will accept taxonomy_term/{uuid}, not taxonomy/term/{uuid}. I guess this is bad DX.

Also, we are not actually meeting the point from the summary where you could call $entity->toUrl('uuid') (I think we weren't in at least the last few versions of the patch, mind you). This could also be a nice candidate for a follow-up, maybe..?

So, remaining tasks:

* Change path processor to base its comparison on canonical route templates for entity types instead of the entity type itself.
* Add some logic to Entity::toUrl to handle $rel='uuid', but base it on the canonical link template (or do this in a follow-up?)

Or, if we really do want to stick to a separate uuid link template, the first point becomes basing the comparison on that instead of the canonical one.

eelkeblok’s picture

Status: Needs work » Needs review
StatusFileSize
new13.49 KB
new2.67 KB

Fixing the left-over test for the UUID route, fixing some code style issues.

Remaining tasks from the previous comment still open, but would like some feedback so marking as needs review anyway.

Edit: Whoops, gave the interdiff a .patch extension...

Status: Needs review » Needs work

The last submitted patch, 248: interdiff-2353611-247-248.patch, failed testing. View results

eelkeblok’s picture

Issue summary: View changes
Status: Needs work » Needs review

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

heddn’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

This is looking good. One small tangible nit below.

Before we get there, I have a small requirement on a certain project to /only/ allow nodes, etc be accessible via the UUID url. And no redirect to/from the canonical URL. Meaning, /node/1 should 404 while /node/1111-222-3333 returns a 200. I don't know that this small use case is all that common, but the current solution also doesn't seem to support it very much either. I went through the last few issue comments to see if this was discussed but didn't see it. Can we have the IS updated to explain the rational for the current approach and the alternatives that were discarded?

Tagging NW for IS update and small nit.

+++ b/core/lib/Drupal/Core/Url.php
@@ -343,8 +343,9 @@ protected static function fromEntityUri(array $uri_parts, array $options, $uri)
+    $route_name = "entity.$entity_type_id.canonical";
...
+    return new static($route_name, [$entity_type_id => $entity_id], $options);

I don't think this is needed. It doesn't change anything.

heddn’s picture

Hmm, seems like what I mentioned in my last comment was discussed in #177, #178 and #181.

sime’s picture

Yes you could do it either with aliases out-of-the-box, or some code to rewrite outbound URLs @heddn. I think we are ok on that front.

cytherion’s picture

Looking at this issue while at DrupalCon Seattle: patch in #248 fails to apply (8.8.0-dev)
error: patch failed: core/lib/Drupal/Core/Entity/Entity.php:318
error: core/lib/Drupal/Core/Entity/Entity.php: patch does not apply

A few deprecation notes:

Entity.php is deprecated in favour of EntityBase per the comments in Entity.php (8.8.0-dev):
@deprecated in Drupal 8.7.0 and will be removed in Drupal 9.0.0. Use \Drupal\Core\Entity\EntityBase instead.

getMock() is deprecated and should be replaced with createMock() or getMockBuilder() per https://www.drupal.org/project/drupal/issues/2929133

mrweiner’s picture

Status: Needs work » Needs review
StatusFileSize
new1.76 KB
new13.5 KB

Re-rolling #248 for 8.8.x. Moved Entity.php updates to apply to EntityBase.php

JayBeeDutch’s picture

StatusFileSize
new8.87 KB

This discussion is now going on for about 5 years.
I'm a totally newbie and haven no experience as an expert in Drupal and PHP. However I'm rather logical. In the response of a JSONAPI call when I look at the href of a relationship I expect the url to get the information of that relation. If I inspect the response the given url is a mixture of the object itself and the url for the relation, i.e. the type/bundle of the relation is given together with the uuid of the object itself.
I expect a url to get/read the relation with JSONAPI. For example a taxonomy term with uuid "tt" belongs to vocabulary with uuid "vv". So the reference should be "http//someserver/jsonapi/vocabulary/vocabulary/vv", now it is "http//someserver/vocabulary/vocabulary/tt?andsomemore"
That doesn't make sense.
Please make clear the "self" and the "relation" url's
A part of the practice in my case:
"data": [
{
"type": "taxonomy_term--blog_categories",
"id": "d8687362-479d-46f4-b4f3-a37959f85a0a",
"attributes": {
"drupal_internal__tid": 2,
"drupal_internal__revision_id": 1,
"langcode": "nl",
"revision_created": "2019-05-05T15:21:07+00:00",
"revision_log_message": "Blog Categories aangemaakt",
"status": true,
"name": "Basis",
"description": null,
"weight": 0,
"changed": "2016-12-02T11:02:11+00:00",
"default_langcode": true,
"revision_translation_affected": true,
"metatag": null,
"path": {
"alias": "\/blog\/categories\/basis",
"pid": 89,
"langcode": "nl"
},
"rh_action": null,
"rh_redirect": null,
"rh_redirect_response": null
},
"relationships": {
"vid": {
"data": {
"type": "taxonomy_vocabulary--taxonomy_vocabulary",
"id": "c322c2f8-fc8d-4148-bf74-5ca1cd7c9f6f"
},
"links": {
"self": {
** this should be the vocabulary???? **
"href": "http:\/\/localhost\/actual\/genesis\/docroot\/jsonapi\/taxonomy_term\/blog_categories\/d8687362-479d-46f4-b4f3-a37959f85a0a\/relationships\/vid"
},
"related": {
** related?? To what?? The uuid doesn't make sense, it's the term, not the vocabulary
"href": "http:\/\/localhost\/actual\/genesis\/docroot\/jsonapi\/taxonomy_term\/blog_categories\/d8687362-479d-46f4-b4f3-a37959f85a0a\/vid"
}
}

eelkeblok’s picture

@JayBeeDutch I am having some trouble understanding how your comment fits in with the current issue, namely allowing a UUID-based URL to access content in addition to the current "local" ID-based one as well as any aliases applying to the URL. Did you mean to comment in a different issue?

@heddn is your use case addressed by #254? I'm wondering whether setting up an alias-scheme using UUIDs wouldn't actually clash with the functionality in this patch. What would you propose otherwise? Can you formulate your requirement in a sentence that would fit in with the other acceptance criteria?

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

erik frèrejean’s picture

StatusFileSize
new465 bytes
new13.5 KB

Just a small nitpick, the patch in #256 introduces a coding standards violation.

Status: Needs review » Needs work

The last submitted patch, 260: drupal-link_uuid-2353611-260.patch, failed testing. View results

erik frèrejean’s picture

StatusFileSize
new2.73 KB
new13.53 KB

Reroll of the patch, the tests used deprecated \Drupal\Tests\PhpunitCompatibilityTrait::getMock() method.

erik frèrejean’s picture

Status: Needs work » Needs review
aaronmchale’s picture

Are the remaining tasks in the issue summary up to date?

I'm also not totally sure how I feel about this just automatically being on for every entity type, I'm not against it, it just makes me feel a little uneasy - for example, if I'm creating an entity type, I expect by default for there to be no assumptions, as in, I have full control over how people interact with the entity, in this case that relates to the link templates, maybe (and this really is just me thinking aloud) this should rely on whether a "cononical-uuid" link template is set so handle it in DefaultHtmlRouteProvider.

Like I said, I'm not 100% sure how I feel about this, will keep thinking on it, but that's my gut feeling above.

dawehner’s picture

Issue summary: View changes

First I tried to update the issue summary with what is in the patch.

I do have one question about this patch: Both the UUID status key and the link template will be optional with this patch.
Given that I'm wondering whether it would be helpful to have some form of checking for those. I know the CR puts this into the calling code, but the PR right now would produce invalid URLs.

One additional question I had: Do we have to worry due to a potential usecase of an existing link template called `uuid` but not actually using UUIDS? At least in contrib this seems not be the case,
see http://codcontrib.hank.vps-private.net/search?text=uuid+%3D+%22&filename=

+++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorEntityUuid.php
@@ -0,0 +1,57 @@
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php

diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index ea552bf4cb..d70bdff683 100644

index ea552bf4cb..d70bdff683 100644
--- a/core/lib/Drupal/Core/Url.php

--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php

+++ b/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -344,8 +344,9 @@ protected static function fromEntityUri(array $uri_parts, array $options, $uri)

@@ -344,8 +344,9 @@ protected static function fromEntityUri(array $uri_parts, array $options, $uri)
     if ($uri_parts['scheme'] != 'entity' || $entity_id === '') {
       throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.");
     }
+    $route_name = "entity.$entity_type_id.canonical";
 
-    return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
+    return new static($route_name, [$entity_type_id => $entity_id], $options);
   }
 
   /**

This seems an unnecessary change :)

danchadwick’s picture

This implementation has an unfortunate characteristic, which I'd call a flaw, in that it only applies to paths of the form ENTITY_TYPE/ENTITY_UUID. If a custom module uses entities in other paths, UUIDs will not be supported.

In my case, I have a path /logbook/{user} for which I'd like UUID support. I simply wrote a ParamConverter for this and I'm wondering if perhaps Drupal\Core\ParamConverter\EntityConverter might be the place to make UUIDs happen.

eelkeblok’s picture

This is actually still having an issue with at least using the UUID as the front page. The user will be redirected to the alias of the page. This is on a site with redirect module enabled, although the difference in behaviour for entering a UUID-based URL vs. a numeric URL in the frontpage field (in the former case, the URL is not changed, whereas in the latter case, the URL is converted to the alias when displaying) leads me to be believe something else is still up.

@DanChadwick Using the paramconverter has been tried before, although I can't recall why that was abandoned. You can read up on it at the start of the issue.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

colan’s picture

c) /uuid/[UUID] (as it's a UUID).

We can't do that as we need to know the entity_type when looking up by uuid.

Not yet, but we should be able to. I think that's ultimately where we want to go, and we shouldn't let the current architecture prevent it. Let's wait until the following are done (although maybe the second one can be done in parallel):

We can then have /entity/UUID redirect to the canonical /node/NID here, and afterwards, say in D10 via #1726734: Replace serial entity IDs with UUIDs in URLs, or even globally?, we can swap those so that /entity/UUID is canonical, and /node/NID redirects to it.

Thoughts?

colan’s picture

Status: Needs review » Postponed
sime’s picture

colan’s picture

Good catch! Updated.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

frob’s picture

What is this issue waiting on?

norman.lol’s picture

Your contribution.

frob’s picture

Status: Postponed » Active

Thank you but I was thinking of something a bit more specific. ;)

According to the IS this issue needs testing of use-cases, but according to #270 the status is Postponed and not Needs Review. Are we waiting till #2690747: #3157869 are done? #2690747: [PLAN] Create an index of UUIDs looks related but I am not sure why #3157869: Store UUIDs in configuration instead of entity IDs is required.

Looks like #2690747: [PLAN] Create an index of UUIDs is stymied should this plan be re-evaluated based on that?

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

ro-no-lo’s picture

After 8 years of back and forth would it not be better to put that functionality into a module put it out into the wild and when the user base has grown and fixed everything merge it into core?

But it could be used already.

ro-no-lo’s picture

A note for everyone looking for a way to load entities by uuid. Currently Drupal has a module which can do that. If you enable the jsonapi module, you can load all kinds of entities by uuid. I tried it only a bit, but the EntityUuidConverter.php is a good start to check if your use case is supported.

Create your route like this:

foo.rest.user.do_something:
  path: '/rest/user-foo-bar/{account}' # account contains the UUID
  defaults:
    _controller: '\Drupal\your_module\Controller\UserController::doSomething'
  methods: [POST]
  requirements:
    _permission: 'access content'
  options:
    parameters:
      account: # here is the path argument again and you define that the jsonapi converter should handle it
        type: entity:user # here you can also say entity:node or entity:comment
        converter: 'paramconverter.jsonapi.entity_uuid' # this converter needs the type parameter to guess the correct entity

In your Controller you can write the action like normal.

public function doSomething(Request $request, AccountInterface $account): JsonResponse {
  ...
}

Note: As long as there are no changes in the jsonapi module. This should work with entities just fine. But even in the converter class it states that it will be dropped when THIS very issue here is solved.

/**
 * Parameter converter for upcasting entity UUIDs to full objects.
 *
 * @internal JSON:API maintains no PHP API since its API is the HTTP API. This
 *   class may change at any time and this will break any dependencies on it.
 *
 * @see https://www.drupal.org/project/drupal/issues/3032787
 * @see jsonapi.api.php
 *
 * @see \Drupal\Core\ParamConverter\EntityConverter
 *
 * @todo Remove when https://www.drupal.org/node/2353611 lands.
 */
class EntityUuidConverter extends EntityConverter {
  $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
  $uuid_key = $this->entityTypeManager->getDefinition($entity_type_id)
    ->getKey('uuid');
  if ($storage = $this->entityTypeManager->getStorage($entity_type_id)) {
    if (!$entities = $storage->loadByProperties([$uuid_key => $value])) {
      return NULL;
    }
    $entity = reset($entities);
  ...
}
mxr576’s picture

Issue summary: View changes

I have read half of the conversation and tried to update the issue summary.

Personally, I fully agree with #266 and therefore #262 is not the right solution for the problem. I also with #271 that #2690747: [PLAN] Create an index of UUIDs is a dependency of this task if we would like to avoid depending/guessing entity_type anyhow when the up-casting happens.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

c-logemann’s picture

This Issue started with this text by @dawehner:

> It would be great to provide URLs with UUIDs in their so you can link to them.

> For poormans menu staging you could use menu_link_config and store refs to UUIDs, like uuid/{entity_type}/{uuid}
which page does a 301 redirect to the actual entity page.

I think this issue should stay on focus with the "{entity_type}/{uuid}" plan. Getting the entity_type removed from the urls is interesting but I think this should move to another issue including all the problems and dependencies like #2690747: [PLAN] Create an index of UUIDs.

Because of symfony and ParamConverter etc. there are many possibilities to fix things in contrib. So I just decided to solve things there. At first I will concentrate on user entities (e.g. "uuid/user/{uuid}") because I need a solution where users should not know their serial uid. After doing some tests today I think this is basically not complicated.

Especially for everything related to nodes, aliases, and redirects things are more complicated. And that's another reason to stay focused on {entity_type} in paths. For me the user part is the most important thing because I don't want to replace the user entity. Since we can create our own entity types with nearly everything a node can do this would be easier. And on the project where I need user/uuid paths I haven't decided yet if I really want to use nodes.

(I will add a comment here when there is a project I will add my solutions.)

didebru’s picture

Issue tags: +Needs reroll
vsujeetkumar’s picture

StatusFileSize
new13.48 KB
new3.68 KB

Re-roll patch created for 11.x.

vsujeetkumar’s picture

StatusFileSize
new13.49 KB
new1.72 KB

Fixed the CCF issue, Please have a look.

vsujeetkumar’s picture

StatusFileSize
new13.44 KB
new778 bytes

Fixed the failed test cases.

c-logemann’s picture

Issue summary: View changes

Thanks @vsujeetkumar for pushing this forward/keeping patch compatible.

I removed Tasks from issue which are not on focus of this issue:

c-logemann’s picture

I'm currently working on a small symfony only project and discovered that it's possible to define multiple routes on an identical path and tested that Drupal 10.3 is compatible with this. For example this documetation:
https://symfony.com/doc/current/routing.html#parameters-validation

I tested the last patch and was wondering why it's working so I found this in the patch code:

/**
+   * {@inheritdoc}
+   */
+  public function processInbound($path, Request $request) {
+    $matches = [];
+    if (preg_match('/^\/([a-z_]+)\/(' . Uuid::VALID_PATTERN . ')$/i', $path, $matches)) {
+      $entity_type_id = $matches[1];
+      $uuid = $matches[2];
+      if ($this->entityTypeManager->hasDefinition($entity_type_id)) {
+        $storage = $this->entityTypeManager->getStorage($entity_type_id);
+        $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+        if ($entity_type->hasLinkTemplate('canonical') && $entities = $storage->loadByProperties(['uuid' => $uuid])) {
+          /** @var \Drupal\Core\Entity\EntityInterface $entity */
+          $entity = reset($entities);
+          $path = '/' . ltrim($entity->toUrl()->getInternalPath(), '/');
+        }
+      }
+    }
+    return $path;
+  }

It's interesting that it's working but with this solution we loose the benefit of routing information and debugging etc. I like the idea to just disable integer routes in custom code to only use uuids.

If we just use an entity_uuid param converter it is possible to use the same controllers for all entity paths e.g. "/user/{user}/edit" with a route 'user.uuid_edit'

I also want to make it possible to avoid user (int) ids in password reset paths where currently {uid} is used. But this maybe something for contrib.

c-logemann’s picture

Assigned: Unassigned » c-logemann

I try to improve in direction of second route as described above.

c-logemann’s picture

Before I start with coding want to add some additional comments after thinking about two routes which allows several thinks like the described identical scheme which is now possible with symfony.
Using two route definitions for each involved entity types makes it possible to realize many things like different access and also different routes. We can also introduce switches to enable uuid Routes of core only if somebody wants. And if somebody want the additional uuid route just as a redirect or with a path pattern like /uuid/user/{uuid}" this can be solved separately in additional code/contrib/custom code etc. So it's possible to solve seo concerns with robots.txt is possible.
In my personal opinion, it's currently a big disadvantage of Drupal that it's not possible to avoid serial IDs especially for users. At any other place I can avoid this easier e.g with not using node and other core entities. The reason why I want to avoid this that I think this is very important for situations when you run a web application where others should not see how many content entities are generated e.g. users, shop carts, issue tickets, mails etc. This gives competitor organizations and persons too much information not everyone likes to share. This is maybe a reason why companies and other organizations didn't choose Drupal.
On the the other side brings a uuid only strategy lots of possibilities to export, archive, re-import or import elsewhere of content without dealing with complex redirection data.
Both areas are my current motivation. Beside users I have already solved everything with custom entities and the wonderful module entity_reference_uuid. But it would be fine to have something more in core like an official uuid param converter. I know Drupal\jsonapi\ParamConverter and maybe start to use this at first.

murz’s picture

By the way, loading entities by UUID is not cached, here is the issue #3308877: Add static cache for loadEntityByUuid function to store uuid-id pairs in memory

c-logemann’s picture

c-logemann’s picture

Assigned: c-logemann » Unassigned

I did some research of the current code and the actual patch. I still like to have an uuid everywhere strategy including revision routes. But currently the serial id strategy is deep integrated in a dynamical way. So a complete new strategy especially as dual solution ha a high complexity. So I think this should be postponed until there is more interest and some discussion and decision by core maintainers.

Meanwhile I will solve the user uuid situation in contrib and just created a new project for this: U3ID. With the U3ID module I can present a prove of concept and maybe something which can move partially to core in future.

I will try to test the current patch against the the dual route concept ASAP on path /user/{user|u3id_user} if there is a conflict which think it will be. Maybe there are other custom solutions out there based on additional routes which can be affected by the PathProcessor solution of the current patch.

c-logemann’s picture

I created additional routes for /user/{uuid} and /user/{uuid}/edit in the u3id module and published already:
https://git.drupalcode.org/project/u3id

1. This is working fine.
2. The pathprocessing from actual patch makes it impossible for such clean route solution. So I strongly recommend to not commit for core. I currently even not recommend to not move to contrib or with add a warning for risks of breaking other code.

There are lot's of concerns against a pure uuid strategy as I read here: #1726734: Replace serial entity IDs with UUIDs in URLs, or even globally?

For getting forward I suggest to focus on a a minimal improvement:
1. First of all we should provide an entity_uuid ParamConverter. For u3id I copied of the core jsonapi: #3310170: Use UUID as entity ID
2. Following the initial feature request we can just provide a possibility to access entities based on additional uuid route configurations.
3. Per default we can just do redirections and introduce settings.php options to change behavior to an entity view.

Everything else should be postponed for core until there is a larger support for more uuid in core routes. I'm currently fine with my contrib project u3id and already solved the problem of rendering the serial uid in the canonical html link with replacing the entity view controller and use now the uuid path.

wim leers’s picture

#3310170: Use UUID as entity ID just landed. Time to reconsider this? 😊

xjm credited catch.

xjm credited star-szr.

xjm’s picture

Adding credits for triage from #105 (outdated though it is now).

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.