Problem/Motivation

Over in #2723579: NodeRouteProvider should extend DefaultHtmlRouteProvider we are trying to deprecate 'node.add_page' and 'node.add' routes.

The problem is these route names are getting used in a lot of places:

  • The URL generation. e.g. Url::fromRoute('node.add_page')
  • Menu Link/local task/local action generation.
  • Drupal::routeMatch()->getRouteName is used in various contexts for doing things conditionally.
  • Breadcrumb builders primarily seem to use route names to derive context.
  • There might be a few more.

Over in #2723579: NodeRouteProvider should extend DefaultHtmlRouteProvider, NodeRouteProcessorBc is added which take care of URL generation but what about the rest?

Proposed resolution

  • Introduce the ability to mark routes as being an alias and (optionally) deprecated in routing.yml files.
  • Internally those deprecations are also converted into aliases, so that the previous (deprecated) route names can still be fetched for calls like getRouteByName()
  • During the routing process those aliases don't make a difference, because their path parameter (table column) is empty
  • When the routes are loaded by their old names (for example in local tasks), a deprecation notice is thrown

Remaining tasks

Figure out all the edge cases by looking at contrib usage of e.g. node.add_page
Fix local tasks when pointing to an alias
Store alias information in the router table
Decide if we want to trigger a deprecation for local tasks that point to an alias
Test coverage for the new methods on RouterProviderInterface
Upgrade path tests

User interface changes

None.

API changes

This will be an API addition. An API to allow the deprecation of the routes.

Data model changes

A new alias column and an index for it are added to the router table.

Release notes snippet

TBD.

Issue fork drupal-3159210

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

jibran created an issue. See original summary.

berdir’s picture

I think the list can be grouped into two cases. URL generation and checking for the current route. Generation is reasonably easy, as done on the node patch. Checking the current route is the problem, and given how the API works, I honestly have no idea how to deal with that. I can't think of anything to make that work. It returns exactly one string that we compare, so that's a hard break.

Either we get an OK from a release manager to make that breaking change, possibly only for a major release or the issue is won't fix and we'll have to live with node not conforming to the naming standard and if necessary find alternative ways to deal with that, like support non-standard route names for link templates somehow.

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.

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.

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.

jibran’s picture

I think we have a way forward here based on https://symfony.com/blog/new-in-symfony-5-4-route-aliasing#deprecating-r.... All we need to add the route alias support to Drupal core.

aaronmchale’s picture

Route Aliasing does look promising, would likely unblock #2723579: NodeRouteProvider should extend DefaultHtmlRouteProvider and make #3153559: Switch Node revision UI to generic UI easier to implement.

catch’s picture

Title: Allow deprecating the route name » Support route aliasing (Symfony 5.4) and allow deprecating the route name
andypost’s picture

Status: Active » Needs review
StatusFileSize
new1.34 KB

Here's initial stub to allow parse aliases - using code from https://github.com/symfony/symfony/pull/38464/files#diff-2b6a8c06ec813f6...

looks like it will throw deprecation on routeCollection::get() so needs some test to make sure it works

andypost’s picture

Issue tags: +Needs tests
StatusFileSize
new3.12 KB
new3.59 KB

Our router table supports to save aliases so reusing current storage

The only issue I see is broken local tasks (no tab displayed at dblog clean-up admin/reports/dblog/confirm)

So it should be covered by tests

Status: Needs review » Needs work

The last submitted patch, 12: 3159210-12.patch, failed testing. View results

jibran’s picture

+++ b/core/modules/dblog/dblog.routing.yml
@@ -7,6 +7,12 @@ dblog.overview:
+  alias: dblog.delete
+  deprecated:
+    package: 'drupal/dblog'
+    version: '10.1'
+
+dblog.delete:

Do we actually want to deprecate this route?

andypost’s picture

@jibran surely no, but this route is handy to test local tasks (is where I stuck in patch)

pwolanin’s picture

Using the built-in symfony mechanism sounds like a the right starting point.

Looking at the patch quickly, I'm concerned that the route saved to the table doesn't have any flag as a separate table column that it's an alias, but i guess the fact that it's not saved with a path or pattern outline means it will never be found doing a lookup by path?

I'd also, ideally, want some way in the route or route match to list the known aliases of a matched route. This could be helpful for e.g. writing a hook_help() or something else looking at the current route name that would work before and after this change.

aaronmchale’s picture

dpi’s picture

We could alias user.well-known.change_password -> user.edit as a working example of aliasing without deprecation.

andypost’s picture

andypost’s picture

@dpi thanks for #18 good idea to have it as alias

@pwolanin do we need a flag in routing table for aliases? I think as the "route" column deserialized we can determine is it alias or route.

larowlan’s picture

Assigned: Unassigned » larowlan
Issue summary: View changes

Updated remaining tasks based on conversation, going to poke at the local tasks issue for a bit

larowlan’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new8.42 KB
new3.59 KB

This addresses @pwolanin's request at #16 and fixes local tasks per #12

To-do - decide if we want to trigger a deprecation when a local task references a deprecated route name.
Tests for the new methods on router provider.

larowlan’s picture

Assigned: larowlan » Unassigned

Done for the time being

longwave’s picture

Status: Needs review » Needs work

Patch in #22 is the same as #12, though the interdiff looks correct.

larowlan’s picture

Status: Needs work » Needs review
StatusFileSize
new10.75 KB

Thanks I git stashed my autoload.php changes before I committed the diff to my local branch, so stashed my changes too

Here's the correct patch for the interdiff above

The last submitted patch, 22: 3159210-22.patch, failed testing. View results

andypost’s picture

Thanks 👍 I bet it needs upgrade hook and test as well because schema change

larowlan’s picture

Issue summary: View changes
Issue tags: +Needs upgrade path tests

The hook is there, but the test is nitt

What do people think about triggering a deprecation if a local task points to an alias?

andypost’s picture

I think every use of deprecated route should cause a warning but it could be overkill for logs.

But local actions like tasks need it but usage in URL generation may need throttling

aaronmchale’s picture

I think every use of deprecated route should cause a warning

Sounds like a good idea.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new4.93 KB

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

pooja saraah’s picture

Status: Needs work » Needs review
StatusFileSize
new10.95 KB
new1.48 KB

Fixed failed commands on #25
Attached patch against Drupal 10.1.x

nod_’s picture

Status: Needs review » Needs work
andypost’s picture

StatusFileSize
new10.68 KB

Closed as duplicate #3218088: Rename the 'alias' option for URLs and proper re-roll, leaving NW for tests

andypost’s picture

All failed tests showing that deprecated route is used somehow, so it needs to clean-up usage

Moreover the error message is not clear enough - no route alias name is displayed (only in argument dblog.delete

Exception: Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'alias' in 'field list': INSERT INTO "test90401139router" ("name", "route", "alias") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2); Array
(
    [:db_insert_placeholder_0] => dblog.confirm
    [:db_insert_placeholder_1] => O:31:"Symfony\Component\Routing\Alias":2:{s:35:"Symfony\Component\Routing\Aliasid";s:12:"dblog.delete";s:44:"Symfony\Component\Routing\Aliasdeprecation";a:3:{s:7:"package";s:12:"drupal/dblog";s:7:"version";s:4:"10.1";s:7:"message";s:106:"The "%alias_id%" route alias is deprecated. You should stop using it, as it will be removed in the future.";}}
    [:db_insert_placeholder_2] => dblog.delete
)

Drupal\Core\Routing\MatcherDumper->dump()() (Line: 164)
andypost’s picture

Better to use route from #18

We could alias user.well-known.change_password -> user.edit as a working example of aliasing without deprecation.

joachim’s picture

The IS doesn't mention the plan that's in the title.

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.

andypost’s picture

Issue tags: +DrupalCon Lille 2023
andypost’s picture

  1. +++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php
    @@ -248,9 +264,17 @@ protected function schemaDefinition() {
    +        'alias' => [
    ...
           'indexes' => [
             'pattern_outline_parts' => ['pattern_outline', 'number_parts'],
    +        'alias' => ['alias'],
    
    +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php
    @@ -476,4 +487,30 @@ protected function getCurrentLanguageCacheIdPart() {
    +  public function getRouteAliases(string $route_name): RouteCollection {
    ...
    +    $routes = $this->connection->select($this->tableName, 'router')
    +      ->fields('router', ['name'])
    +      ->condition('alias', $route_name)
    ...
    +    $select = $this->connection->select($this->tableName, 'router')
    +      ->fields('router', ['alias'])
    +      ->condition('name', $aliased_route_name);
    
    +++ b/core/modules/system/system.post_update.php
    @@ -51,3 +51,11 @@ function system_post_update_linkset_settings() {
    +function system_post_update_rebuild_matcher_dumper_with_alias_support(): void {
    +  \Drupal::database()->schema()->dropTable('router');
    

    new column and index - schema change should go to update hook I bet and rebuild probably will be automatic

    also it needs upgrade test

  2. +++ b/core/lib/Drupal/Core/Routing/RouteProviderInterface.php
    @@ -104,4 +105,29 @@ public function getAllRoutes();
    +  public function getRouteAliases(string $route_name): RouteCollection;
    ...
    +  public function getUnAliasedRouteName(string $aliased_route_name): ?string;
    

    the only API addition

_utsavsharma’s picture

StatusFileSize
new10.69 KB
new10.69 KB

Patch for 11.x, as the previous was failing to apply.

duadua’s picture

Issue summary: View changes
   // Not using chunks because not many aliases expected.
+      $insert = $this->connection->insert($this->tableName)->fields([
+        'name',
+        'route',
+        'alias',
+      ]);

I'm coming here from https://www.drupal.org/project/drupal/issues/3311365. It seems like this assumption might not hold necessarily?

+/**
+ * Rebuilds the router with alias support.
+ */
+function system_post_update_rebuild_matcher_dumper_with_alias_support(): void {
+  \Drupal::database()->schema()->dropTable('router');
+  \Drupal::service('router.builder')->rebuild();
+}

Is this a case where it would be better to use ->setRebuildNeeded(). Looking at what post update is doing, it does flush all caches afterwards, which would rebuild the router if needed.

amateescu’s picture

Issue tags: -Needs upgrade path tests
StatusFileSize
new9.97 KB
new10.32 KB

Fixed the feedback from #41 and #43.

Local tasks are not broken for an aliased route, so I removed the change from LocalTaskManager and the new getUnAliasedRouteName() which didn't have a purpose anymore.

For aliasing user.well-known.change_password to user.edit, after reading https://symfony.com/blog/new-in-symfony-5-4-route-aliasing I think that's not a valid example, because the two routes are not pointing to the same (URL) path.

Wrote an upgrade path test, so we only need some test coverage for the new RouteProvider::getRouteAliases() method now.

As for triggering deprecation errors for local tasks, I don't think that's necessary, the current trigger_error() from RouteProvider::getRouteByName() is enough IMO. And combined with #3024296: Add option to log deprecation errors, it should provide enough visibility for deprecated routes.

andypost’s picture

Thank you! Still needs work to fix CS and

only need some test coverage for the new RouteProvider::getRouteAliases() method now.

MacSim made their first commit to this issue’s fork.

andypost’s picture

and it shou8ld prove a test with deprecated route - let it be test module

andypost’s picture

andypost’s picture

hide patches as MR is used

amateescu’s picture

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

Added some test coverage in \Drupal\Tests\system\Functional\Routing\RouterTest but I think we need to add more in \Drupal\KernelTests\Core\Routing\RouteProviderTest, so leaving at NW.

amateescu’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs tests

Added the remaining test coverage needed for \Drupal\Core\Routing\RouteProvider::getRouteAliases(), the MR is ready for final reviews now!

andypost’s picture

Looks great and ready to go!

Just not sure about return type which I think which be iterable (traversable) instead of array

acbramley made their first commit to this issue’s fork.

acbramley’s picture

andypost’s picture

the only question is array vs iterable

andypost’s picture

Status: Needs review » Needs work

any somehow update hooks fails on mysql

Run updates
       ┐
       ├ The update failed with the following message: "Failed: Drupal\Core\Database\SchemaException: MySQL needs the 'alias' field specification in order to normalize the 'alias' index in Drupal\mysql\Driver\Database\mysql\Schema->getNormalizedIndexes() (line 332 of /builds/issue/drupal-3159210/core/modules/mysql/src/Driver/Database/mysql/Schema.php)."
       │
       │ /builds/issue/drupal-3159210/core/tests/Drupal/Tests/UpdatePathTestTrait.php:68
       │ /builds/issue/drupal-3159210/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php:196
       │ /builds/issue/drupal-3159210/core/modules/workspaces/tests/src/Functional/Update/WorkspaceAssociationStringIdsUpdatePathTest.php:42
andypost’s picture

sqlite pass all

upgrade fails on mysql (every upgrade test) https://git.drupalcode.org/issue/drupal-3159210/-/jobs/4004297

    Search Block Page Id Update Path (Drupal\Tests\search\Functional\Update\SearchBlockPageIdUpdatePath)
     ✘ Run updates
       ┐
       ├ The update failed with the following message: "Failed: Drupal\Core\Database\SchemaException: MySQL needs the 'alias' field specification in order to normalize the 'alias' index in Drupal\mysql\Driver\Database\mysql\Schema->getNormalizedIndexes() (line 332 of /builds/issue/drupal-3159210/core/modules/mysql/src/Driver/Database/mysql/Schema.php)."
       │
       │ /builds/issue/drupal-3159210/core/tests/Drupal/Tests/UpdatePathTestTrait.php:68
       │ /builds/issue/drupal-3159210/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBase.php:196
       │ /builds/issue/drupal-3159210/core/modules/search/tests/src/Functional/Update/SearchBlockPageIdUpdatePathTest.php:33
       ┴

pgsql fails only 1 test RouteProviderTest https://git.drupalcode.org/issue/drupal-3159210/-/jobs/4004507

     ✔ Outline path match zero
     ✔ Outline path no match
     ✔ Route caching
     ✔ Route by name
     ✔ Get routes by pattern with long patterns
     ✘ Route aliases
       ┐
       ├ Error: Call to a member function getId() on false
       │
       │ /builds/issue/drupal-3159210/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php:767
       ┴
    
    1 test triggered 1 PHP warning:
    
    1) /builds/issue/drupal-3159210/core/lib/Drupal/Core/Routing/RouteProvider.php:267
    unserialize(): Error at offset 44 of 48 bytes
    
    Triggered by:
    
    * Drupal\KernelTests\Core\Routing\RouteProviderTest::testRouteAliases (2 times)
      /builds/issue/drupal-3159210/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php:755
catch’s picture

  ├ The update failed with the following message: "Failed: Drupal\Core\Database\SchemaException: MySQL needs the 'alias' field specification in order to normalize the 'alias' index in Drupal\mysql\Driver\Database\mysql\Schema->getNormalizedIndexes() (line 332 of /builds/issue/drupal-3159210/core/modules/mysql/src/Driver/Database/mysql/Schema.php)."

To me this looks broken in the MySQL driver.

::addField() calls ::createKeysSql(), ::createKeysSql() calls ::getNormalizedIndexes(), ::getNormalizedIndexes() expects a $spec['fields'] to be passed in with the field definition(s) and that is not done.

andypost’s picture

@amateescu Thank you fixing update hook! Probably it needs follow-up and todo added to allow add field with index to mysql table in one transaction

andypost’s picture

The only failure is pgsql with strange warning

    1) /builds/issue/drupal-3159210/core/lib/Drupal/Core/Routing/RouteProvider.php:267
    unserialize(): Error at offset 44 of 48 bytes
andypost’s picture

on pgsql able to reproduce, the failing value is

php -r 'var_dump(unserialize(hex2bin("4f3a33313a2253796d666f6e795c436f6d706f6e656e745c526f7574696e675c416c696173223a323a7b733a34343a22")));'
PHP Warning:  unserialize(): Error at offset 44 of 48 bytes in Command line code on line 1
PHP Stack trace:
PHP   1. {main}() Command line code:0
PHP   2. unserialize($data = 'O:31:"Symfony\\Component\\Routing\\Alias":2:{s:44:"') Command line code:1
Command line code:1:
bool(false)
andypost’s picture

amateescu’s picture

Status: Needs work » Needs review

@andypost, thanks for fixing the pgsql issue, I was really scratching my head around it. Reverted your latest commit, we can't add them at the same time until the issue you just opened is fixed, and it's also not worth waiting for it :)

daffie’s picture

Status: Needs review » Needs work

Back to needs work for my remark on the PR.

amateescu’s picture

Status: Needs work » Needs review

Replied to the MR comment.

andypost’s picture

Status: Needs review » Reviewed & tested by the community

I think it's ready for commiters

catch’s picture

Status: Reviewed & tested by the community » Needs review

One question.

There is no change to RouteProvider::getRoutesByPath(), shouldn't be filtering aliases in any case where we're not explicitly loading the route by name? If not in that method then somewhere else?

amateescu’s picture

Status: Needs review » Reviewed & tested by the community

@catch, there's no need to do any additional filtering in RouteProvider::getRoutesByPath() because we only store data in the name, route and alias columns, so the current conditions on pattern_outline or number_parts already exclude aliases.

Another place would be ::preLoadRoutes(), but that's called by ::getRoutesByNames() (which in turn is used by ::getRouteByName() and the new ::getRouteAliases()), so no need to filter there.

And then there's ::getAllRoutes() which is documented to return an array of Route objects and has this doxygen:

   * Usage of this method is discouraged for performance reasons. If possible,
   * use RouteProviderInterface::getRoutesByNames() or
   * RouteProviderInterface::getRoutesByPattern() instead.

Added a filter in ::getAllRoutes() and moving back to RTBC because I don't think this change requires test coverage.

  • catch committed 6653fab3 on 11.x
    Issue #3159210 by amateescu, andypost, acbramley, larowlan, pooja saraah...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for #69 that all looks sensible.

No other complaints, so committed/pushed to 11.x, thanks!

quietone’s picture

I updated the CR to have two examples and to include the message property that was missing. Also, added this to 'How to deprecate' and added alias to Structure of routes.

catch’s picture

Thanks @quietone!

Status: Fixed » Closed (fixed)

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