Problem/Motivation

As a result of #2339219: [meta] Finalize URL generation API (naming, docs, deprecation), we've standardized on the \Drupal\Core\Url object as The Way™ to deal with URLs. Even \Drupal::l() takes a Url object.

However, the most common way of printing links, '#type' => 'link', does not support a URL. Instead, they still support the legacy #href, as well as the intermediary #route_name/#route_parameters pair.

This also extends to the very common '#theme' => 'links', which internally uses '#type' => 'link'.

Proposed resolution

Remove support for both #href and #route_name from render arrays using '#type' => 'link'
Remove support for both href and route_name from render arrays using '#theme' => 'links'

Remaining tasks

None

User interface changes

API changes

  • Url::toArray() and Url::toRenderArray() now return an array containing the Url object itself, and are largely unnecessary now.
  • Render arrays using '#type' => 'link' and '#theme' => 'links' no longer support href or route_name, only #url/url
  • \Drupal\language\LanguageSwitcherInterface::getLanguageSwitchLinks() and \Drupal\Core\Language\LanguageManagerInterface::getLanguageSwitchLinks() take a Url object instead of a path.
  • \Drupal\Core\Menu\MenuLinkInterface::getDeleteRoute(), \Drupal\Core\Menu\MenuLinkInterface::getEditRoute(), and \Drupal\Core\Menu\MenuLinkInterface::getTranslateRoute() return a Url object instead of an array
  • The config_path annotation used for LanguageNegotiationMethodInterface plugins is now config_route_name
CommentFileSizeAuthor
#84 interdiff.txt875 bytestim.plunkett
#84 2347465-type-link-84.patch195.61 KBtim.plunkett
#82 2347465-type-link-82.patch195.6 KBtim.plunkett
#82 interdiff.txt4.27 KBtim.plunkett
#79 interdiff.txt776 bytestim.plunkett
#79 2347465-type-link-79.patch192.62 KBtim.plunkett
#71 interdiff.txt854 bytestim.plunkett
#71 2347465-type-link-71.patch192.89 KBtim.plunkett
#69 interdiff.txt1.3 KBtim.plunkett
#69 2347465-type-link-69.patch193 KBtim.plunkett
#67 interdiff.txt4.42 KBtim.plunkett
#67 2347465-type-link-67.patch192.39 KBtim.plunkett
#66 interdiff.txt862 bytestim.plunkett
#66 2347465-type-link-66.patch195.66 KBtim.plunkett
#64 2347465-type-link-64.patch195.88 KBtim.plunkett
#59 2347465-59.patch195.17 KBcilefen
#57 interdiff.txt367 bytesdawehner
#57 2347465-57.patch195.39 KBdawehner
#55 interdiff.txt1.27 KBdawehner
#55 2347465-55.patch195.58 KBdawehner
#52 interdiff.txt9.58 KBdawehner
#52 2347465-52.patch194.75 KBdawehner
#48 interdiff.txt1.92 KBtim.plunkett
#48 2347465-type-link-48.patch193.35 KBtim.plunkett
#47 interdiff.txt62.59 KBtim.plunkett
#47 2347465-type-link-47.patch202.52 KBtim.plunkett
#47 2347465-type-link-47-debug_exceptions.patch203.66 KBtim.plunkett
#44 interdiff.txt50.35 KBdawehner
#44 2347465-44.patch144.37 KBdawehner
#42 interdiff.txt1.39 KBtim.plunkett
#42 2347465-type-link-42.patch116.57 KBtim.plunkett
#39 2347465-type-link-40.patch116.58 KBtim.plunkett
#39 interdiff.txt1.9 KBtim.plunkett
#38 interdiff.txt1000 bytestim.plunkett
#38 2347465-type-link-39.patch117.3 KBtim.plunkett
#37 interdiff.txt5.44 KBtim.plunkett
#37 2347465-type-link-37.patch117.28 KBtim.plunkett
#34 interdiff.txt3.71 KBtim.plunkett
#34 2347465-type-link-34.patch113.1 KBtim.plunkett
#32 interdiff.txt2.23 KBtim.plunkett
#32 2347465-type-link-32.patch112.85 KBtim.plunkett
#29 interdiff.txt881 bytestim.plunkett
#29 2347465-type-link-29.patch133.66 KBtim.plunkett
#26 interdiff.txt37.1 KBtim.plunkett
#26 2347465-type-link-26.patch133.81 KBtim.plunkett
#23 interdiff.txt4.46 KBtim.plunkett
#23 2347465-type-link-23.patch106.47 KBtim.plunkett
#20 interdiff.txt5 KBdawehner
#20 2347465-20.patch104.1 KBdawehner
#18 2347465-18.patch104.32 KBdawehner
#16 interdiff.txt5 KBdawehner
#16 2347465-16.patch104.11 KBdawehner
#13 2347465-type-link-13.patch101.05 KBtim.plunkett
#13 interdiff.txt20.8 KBtim.plunkett
#11 interdiff.txt2.25 KBtim.plunkett
#11 2347465-type-link-11.patch98.45 KBtim.plunkett
#9 2347465-type-link-9.patch96.36 KBtim.plunkett
#9 interdiff.txt63.9 KBtim.plunkett
#3 2347465-2.patch36.79 KBdawehner
#1 2347465-1.patch1.81 KBdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

Status: Active » Needs review
FileSize
1.81 KB

Let's start to see how much breaks with just that.

Working on actually fixing it.

dawehner’s picture

Status: Needs work » Needs review
FileSize
36.79 KB

Some work done, more to come.

pwolanin’s picture

can you push a sandbox branch?

tim.plunkett’s picture

  1. +++ b/core/modules/aggregator/src/FeedViewBuilder.php
    @@ -99,7 +100,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
    +            '#href' => Url::fromUri($link_href),
    
    +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -271,6 +272,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
             '#href' => 'admin/people/permissions',
    +        '#url' => Url::fromRoute('user.admin_permissions'),
    

    Extra #href, one missing conversion.

  2. +++ b/core/modules/toolbar/src/Element/ToolbarItem.php
    @@ -33,7 +33,6 @@ public function getInfo() {
             '#type' => 'link',
             '#title' => NULL,
    -        '#href' => '',
    
    +++ b/core/modules/toolbar/tests/modules/toolbar_test/toolbar_test.module
    @@ -17,7 +17,7 @@ function toolbar_test_toolbar() {
           '#type' => 'link',
           '#title' => t('Test tab'),
    -      '#href' => '',
    +      '#url' => '',
    

    What's the difference between these?

tim.plunkett’s picture

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

Working on this.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
63.9 KB
96.36 KB

Whew. Quite the can of worms.

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned
Status: Needs work » Needs review
FileSize
98.45 KB
2.25 KB

That's probably it for me tonight.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
20.8 KB
101.05 KB

One more for the road.

dawehner’s picture

Assigned: Unassigned » dawehner

Thank you for taking this over yesterday, working on it now.

dawehner’s picture

Status: Needs work » Needs review
FileSize
104.11 KB
5 KB

Let's see how much else is left.

dawehner’s picture

Priority: Major » Critical
Status: Needs work » Needs review
FileSize
104.32 KB

Let's quickly reroll it. I consider it critical to actually ensure that our route based system works.

dawehner’s picture

Assigned: dawehner » Unassigned
Status: Needs work » Needs review
FileSize
104.1 KB
5 KB

Doh, so unproductive!

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

Thanks @dawehner, those fixes should help a lot. My turn again!

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
106.47 KB
4.46 KB
tim.plunkett’s picture

#2345753: Remove url(current_path()) and url(NULL) with <current> and <none> might have gotten in the way if we did it later, so pulling parts of it in.
I'm still working on this, in a test issue.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
133.81 KB
37.1 KB
tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned

I can't deal with this issue more tonight. Good luck $whoever_is_next!

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
133.66 KB
881 bytes

Okay a bit more before bed.

dawehner’s picture

Assigned: Unassigned » dawehner

Working on it a bit.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
112.85 KB
2.23 KB

That was about 3 hours ago, and you're not online, so I'm going to get back to posting patches for this. Hopefully we don't overlap too much.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
113.1 KB
3.71 KB
tim.plunkett’s picture

Assigned: dawehner » tim.plunkett

Still working on this. Getting close!

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
117.28 KB
5.44 KB

Hmmm, UnroutedUrlAssembler::buildLocalUrl needs to do more of what UrlGenerator::generateFromPath does...

tim.plunkett’s picture

Status: Needs work » Needs review
Issue tags: +Needs issue summary update
FileSize
117.3 KB
1000 bytes
tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned
FileSize
1.9 KB
116.58 KB

Here it is without the exceptions I used to track things down.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
116.57 KB
1.39 KB

It's the little things.

jibran’s picture

+++ b/core/includes/theme.inc
@@ -1018,16 +1019,19 @@ function template_preprocess_links(&$variables) {
+          // @todo System path is deprecated - use the route name and parameters.

We need an issue for this.

dawehner’s picture

FileSize
144.37 KB
50.35 KB

Let's see how much this stops.

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

Working on this

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned
Status: Needs work » Needs review
FileSize
203.66 KB
202.52 KB
62.59 KB

Keep in mind this still removes CommentLinkBuilderTest, which I have NO idea how to fix.

tim.plunkett’s picture

Yay! I was able to figure out CommentLinkBuilderTest. It was easier to debug when I reduced the permutations down to 1 each, and found the exact combination that was failing.
Moral of the story: don't mock value objects.

(Also, I switched the order of the assertion since PHPUnit makes a distinction between expected/actual.

tim.plunkett’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update +undefined

API-wise, this should have been a beta blocker. Better late than never.

tim.plunkett’s picture

Issue tags: -undefined +API change

Weird tag bug.

pwolanin’s picture

shoudl we remove toArray() and toRenderArray() all together?

dawehner’s picture

FileSize
194.75 KB
9.58 KB

shoudl we remove toArray() and toRenderArray() all together?

I decided to not do that for the reason of release management, deprecated can be removed and decided later.

@tim
No attack here on your work, some changes are needed, some are for research.

tim.plunkett’s picture

Just as long as it gets back to green before long, research away.

I removed verbose calls because we don't usually force debug output into our tests, and because it blows up on Url

dawehner’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
195.58 KB
1.27 KB

I removed verbose calls because we don't usually force debug output into our tests, and because it blows up on Url

Well, I hate to remove stuff which is a) helpful and b) we should fix the actual underlying problem, don't we? (yeah var_export is in general a bad idea).

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Url.php
@@ -473,6 +478,10 @@ public function toArray() {
+  public function __toString() {
+    return 'bloub';
+  }

?

dawehner’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
195.39 KB
367 bytes

This is not needed any longer.

cilefen’s picture

Status: Needs work » Needs review
FileSize
195.17 KB

Rerolled because #2326409: Annotate render element plugins removed hook_element_info() from the toolbar.module.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

RERTBC

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/core.services.yml
@@ -491,7 +491,7 @@ services:
-    arguments: ['@request_stack', '@config.factory' ]
+    arguments: ['@path_processor_manager', '@request_stack', '@config.factory']

I think we've agreed that path processing will not be done for unrouted url's.

tim.plunkett’s picture

Our current implementation of shortcut paths considers them unrouted, by using base://, and they need language processing. Otherwise, someone needs to rewrite shortcut module, I guess?

tim.plunkett’s picture

  1. +++ b/core/lib/Drupal/Core/Url.php
    @@ -456,12 +456,26 @@ public function toString() {
    -      'url' => $this,
    ...
    +        'route_name' => $this->getRouteName(),
    +        'route_parameters' => $this->getRouteParameters(),
    +        'options' => $this->getOptions(),
    

    This needs to be reverted, we just removed support for 'route_name'.

  2. +++ b/core/lib/Drupal/Core/Url.php
    @@ -456,12 +456,26 @@ public function toString() {
    +        // @todo Change 'path' to 'href': https://www.drupal.org/node/2347025.
    +        'path' => $this->getUri(),
    

    I don't know why we're bringing this back.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
195.88 KB
tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
195.66 KB
862 bytes

Forgot the interdiff, and wasn't up-to-date with HEAD.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
192.39 KB
4.42 KB

Oh, actually this should work. Forgot that all of the routing changes were done to Shortcut, and the path is just computed.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
193 KB
1.3 KB

Weird. TypedData is adding an extra 'value' => [] to the route_parameters, which breaks the comparison if ($shortcut->getRouteName() == $url->getRouteName() && $shortcut->getRouteParams() == $url->getRouteParameters()) {

Why that is happening now and not in HEAD, I do not know.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
192.89 KB
854 bytes

Oh! That would be why. Shortcut::setRouteParams is wrapping the array in an array for no reason.
Yay for finding bugs.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

This should adequately address @alexpott's feedback.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/block/block.module
@@ -93,10 +93,15 @@ function block_page_build(&$page) {
-      '#href' => 'admin/structure/block' . (\Drupal::config('system.theme')->get('default') == $theme ? '' : '/list/' . $theme),
...
+    if (\Drupal::config('system.theme')->get('default') == $theme) {
+      $page['page_top']['backlink']['#url'] = Url::fromRoute('block.admin_display_theme', ['theme' => $theme]);
+    }
+    else {
+      $page['page_top']['backlink']['#url'] = Url::fromRoute('block.admin_display');
+    }

This seems to be the wrong way around?

I need more time to review this patch.

pwolanin’s picture

Shortcut thinks should use routes - or if not, they should not get any path processing applied.

Anything using base:// is like hard-coding a link.

catch’s picture

I'd like to hold off committing this patch until we've got solid agreement on #2351379: [meta] Define, then support exact use cases for link generation and storage.

After that while I expect we'll need to convert a lot of these cases, we might find specific examples that need to be moved to non-routes. Or if we do go ahead with the patch, we might have to go through and convert some cases a second time.

tim.plunkett’s picture

#74, I fixed shortcut, that's using routes, and I removed the path processing I added.

The only usages of base:// added in this patch are:
Drupal\views\Plugin\views\Field\Links::getLinks() where we deal with a string path
Two places we handle ?destination=, which must be a string path.

If you hold off on this patch past Thursday, you'll need to find someone else to work on it, I'll be gone until late October after this week. It'd be a shame to see this massive improvement fall stale and not get in.

catch’s picture

@Tim can you look at #2351379: [meta] Define, then support exact use cases for link generation and storage then? It has a comprehensive issue summary and no big long discussion to wade through. Also enjoy the break :)

tim.plunkett’s picture

I think this issue makes that one easier, not harder.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
192.62 KB
776 bytes

Status: Needs review » Needs work

The last submitted patch, 79: 2347465-type-link-79.patch, failed testing.

catch’s picture

Looking at the patch I do think this makes the other issue easier rather than harder.

Nearly all the changes are the specific use case of linking to a route very much coupled to a module, it's not the user-entered route use-cases that are proving difficult to support with routes in the other issue. Also some valid uses of base::// for destination which I don't think we'll need to change either.

So yes (unless someone has a concrete objection I missed), let's keep going here and continue in the other issue dealing with menu links/shortcuts and similar cases where the pain is.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
4.27 KB
195.6 KB

Added a workaround for a random bug.
I tested the patch in #2352791: Add test coverage for admin/structure/block/demo with the patch from #71, and it catches the bug, and passes with the fix in #79.

Additionally, added @todos for the known places we'd want to stop using with fromUri().

dawehner’s picture

  1. +++ b/core/modules/book/book.module
    @@ -110,15 +111,17 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
    -            'href' => 'node/add/' . $child_type,
    -            'query' => array('parent' => $node->id()),
    +            'url' => Url::fromRoute('node.add', ['node_type' => $child_type], ['parent' => $node->id()]),
    

    note: you have to use 'query' => ['parent' => $node->id()) here.

  2. +++ b/core/modules/config_translation/src/ConfigEntityMapper.php
    @@ -216,7 +217,9 @@ public function getOperations() {
    diff --git a/core/modules/config_translation/src/ConfigNamesMapper.php b/core/modules/config_translation/src/ConfigNamesMapper.php
    
    diff --git a/core/modules/config_translation/src/ConfigNamesMapper.php b/core/modules/config_translation/src/ConfigNamesMapper.php
    index 57811d9..ed796af 100644
    
    index 57811d9..ed796af 100644
    --- a/core/modules/config_translation/src/ConfigNamesMapper.php
    
    --- a/core/modules/config_translation/src/ConfigNamesMapper.php
    +++ b/core/modules/config_translation/src/ConfigNamesMapper.php
    
    +++ b/core/modules/config_translation/src/ConfigNamesMapper.php
    +++ b/core/modules/config_translation/src/ConfigNamesMapper.php
    @@ -15,6 +15,7 @@
    
    @@ -15,6 +15,7 @@
     use Drupal\Core\Routing\RouteProviderInterface;
     use Drupal\Core\StringTranslation\TranslationInterface;
     use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
    +use Drupal\Core\Url;
     use Drupal\locale\LocaleConfigManager;
     use Symfony\Component\DependencyInjection\ContainerInterface;
     use Symfony\Component\HttpFoundation\Request;
    @@ -200,7 +201,7 @@ protected function processRoute(Route $route) {
    
    @@ -200,7 +201,7 @@ protected function processRoute(Route $route) {
        * {@inheritdoc}
        */
       public function getBasePath() {
    -    return $this->getPathFromRoute($this->getBaseRoute(), $this->getBaseRouteParameters());
    +    return Url::fromRoute($this->getBaseRouteName(), $this->getBaseRouteParameters())->getInternalPath();
       }
     
       /**
    @@ -237,7 +238,7 @@ public function getOverviewRoute() {
    
    @@ -237,7 +238,7 @@ public function getOverviewRoute() {
        * {@inheritdoc}
        */
       public function getOverviewPath() {
    -    return $this->getPathFromRoute($this->getOverviewRoute(), $this->getOverviewRouteParameters());
    +    return Url::fromRoute($this->getOverviewRouteName(), $this->getOverviewRouteParameters())->getInternalPath();
       }
     
    
    @@ -335,25 +336,6 @@ public function getDeleteRoute() {
       /**
    -   * Gets the path for a certain route, given a set of route parameters.
    -   *
    -   * @param \Symfony\Component\Routing\Route $route
    -   *   The route object.
    -   * @param array $parameters
    -   *   An array of route parameters.
    -   *
    -   * @return string
    -   *   Processed path with placeholders replaced.
    -   */
    -  public function getPathFromRoute(Route $route, array $parameters) {
    -    $path = $route->getPath();
    -    foreach ($parameters as $key => $value) {
    -      $path = str_replace('{' . $key . '}', $value, $path);
    -    }
    -    return $path;
    -  }
    -
    -  /**
    ...
    @@ -492,7 +474,7 @@ public function getOperations() {
    
    @@ -492,7 +474,7 @@ public function getOperations() {
         return array(
           'translate' => array(
             'title' => $this->t('Translate'),
    -        'href' => $this->getOverviewPath(),
    +        'url' => Url::fromRoute($this->getOverviewRouteName(), $this->getOverviewRouteParameters()),
           ),
         );
       }
    

    I am not convinced by that change. This is highly problematic as this functions are executed during routing rebuild.

tim.plunkett’s picture

FileSize
195.61 KB
875 bytes

None of the get*Path() methods are called during routing, just get*RouteName() and get*Route().

Opened #2353029: Book "Add child page" link query string needs test coverage to add test coverage.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

None of the get*Path() methods are called during routing, just get*RouteName() and get*Route().

Ah I see,

alexpott’s picture

Status: Reviewed & tested by the community » Fixed
Related issues: +#2351379: [meta] Define, then support exact use cases for link generation and storage

Thanks for opening the issues to improve test coverage.

I agree with @catch in #81. I don't any reason to hold this up based on #2351379: [meta] Define, then support exact use cases for link generation and storage

alexpott’s picture

Committed 195d499 and pushed to 8.0.x. Thanks!

  • alexpott committed 195d499 on 8.0.x
    Issue #2347465 by tim.plunkett, dawehner, cilefen: Convert all instances...
hass’s picture

+++ b/core/includes/theme.inc
@@ -902,14 +902,8 @@ function template_preprocess_status_messages(&$variables) {
+ *     - url: (optional) The url object to link to. If omitted, no a tag is
+ *       printed out.

Can we change this wording a bit, please? I have read it 3-4 times to understand that this is not a typo. Maybe something like "If omitted, no <a>-tag is printed out."?

xjm’s picture

I'm guessing #82 missed an instance of the random fail and so this introduced: #2353457: Random test failure in Drupal\views_ui\Tests\DisplayPathTest

catch’s picture

larowlan’s picture

Published change notice (tracked down to this after finding all my contrib list builder operations broken).

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

YesCT’s picture

oops, removed the deprecated toArray() from the test that was to test toArray(). see #2417445: Remove Url::toArray()