Problem/Motivation

Now that path aliases have been converted to entities in #2336597: Convert path aliases to full featured entities, we can convert their custom forms to use content entity forms instead.

Proposed resolution

Convert custom path alias forms to entity forms.

The following table lists all the previous form classes and their IDs, along with their replacements.

Previous form class Previous form ID   New form class New form ID
Drupal\path\Form\AddForm path_admin_add -> Drupal\path\PathAliasForm path_alias_form
Drupal\path\Form\EditForm path_admin_edit -> Drupal\path\PathAliasForm path_alias_form
Drupal\path\Form\DeleteForm path_alias_delete -> Drupal\Core\Entity\ContentEntityDeleteForm path_alias_delete_form
Drupal\path\Form\PathFormBase N/A -> Drupal\path\PathAliasForm N/A

Custom code needs to be updated for any hook_form_alter() or hook_form_FORM_ID_alter() implementations that were using the previous form IDs. For example, a hook_form_path_admin_add_alter() implementation of hook_form_FORM_ID_alter() for the path_admin_add form ID needs to be renamed to hook_form_path_alias_form_alter().

Additionally, the following routes have been deprecated and replaced by generic entity routes:

Previous route name New route name
path.admin_add entity.path_alias.add_form
path.admin_edit entity.path_alias.edit_form
path.delete entity.path_alias.delete_form
path.admin_overview entity.path_alias.collection

Remaining tasks

Review, commit.

User interface changes

Nope.

API changes

Nope.

Data model changes

Nope.

Release notes snippet

Path alias administrative forms have been converted to generic entity forms. This means form IDs and form class names have changed, so custom code needs to be updated for any hook_form_alter() or hook_form_FORM_ID_alter() implementations that were using the previous form IDs. Additionally, some path routes have been deprecated and replaced by generic entity routes: See the change record for a full list of changes.

CommentFileSizeAuthor
#33 interdiff-33.txt5.1 KBamateescu
#33 3007832-33.patch61.26 KBamateescu
#30 interdiff-30.txt6.45 KBamateescu
#30 3007832-30.patch62.35 KBamateescu
#28 interdiff-28.txt746 bytesamateescu
#28 3007832-28.patch55.9 KBamateescu
#26 interdiff-26.txt3 KBamateescu
#26 3007832-26.patch55.91 KBamateescu
#20 3007832-20-combined.patch152.98 KBamateescu
#20 3007832-20-for-review.patch55.92 KBamateescu
#19 3007832-19-combined.patch160.77 KBamateescu
#17 3007832-17-combined.patch158.91 KBamateescu
#16 interdiff-16.txt2.58 KBamateescu
#16 3007832-16-combined.patch153.61 KBamateescu
#16 3007832-16.patch55.97 KBamateescu
#14 3007832-14-combined.patch152.04 KBamateescu
#14 3007832-14-for-review.patch56.02 KBamateescu
#12 interdiff-12.txt1.36 KBamateescu
#12 3007832-12-combined.patch132.79 KBamateescu
#12 3007832-12-for-review.patch57.5 KBamateescu
#11 interdiff-11.txt999 bytesamateescu
#11 3007832-11-combined.patch133.05 KBamateescu
#11 3007832-11-for-review.patch57.51 KBamateescu
#10 interdiff-10.txt3.55 KBamateescu
#10 3007832-10-combined.patch132.65 KBamateescu
#10 3007832-10-for-review.patch57.33 KBamateescu
#9 3007832-9-combined.patch132.01 KBamateescu
#7 interdiff-7.txt709 bytesamateescu
#7 3007832-7-combined.patch132.08 KBamateescu
#7 3007832-7-for-review.patch57.56 KBamateescu
#5 interdiff-5.txt936 bytesamateescu
#5 3007832-5-combined.patch133.95 KBamateescu
#5 3007832-5-for-review.patch57.53 KBamateescu
#4 interdiff-4.txt24.37 KBamateescu
#4 3007832-4-combined.patch133.95 KBamateescu
#4 3007832-4-for-review.patch57.53 KBamateescu
#2 3007832-2-combined.patch120.49 KBamateescu
#2 3007832-2-for-review.patch40.44 KBamateescu
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

amateescu created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review
FileSize
40.44 KB
120.49 KB

Worked a bit on this today.

Status: Needs review » Needs work

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

amateescu’s picture

Status: Needs work » Needs review
FileSize
57.53 KB
133.95 KB
24.37 KB

Fixed a few things.

amateescu’s picture

Status: Needs review » Needs work

The last submitted patch, 5: 3007832-5-combined.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review
FileSize
57.56 KB
132.08 KB
709 bytes

Oops.

Status: Needs review » Needs work

The last submitted patch, 7: 3007832-7-combined.patch, failed testing. View results

amateescu’s picture

Status: Needs work » Needs review
FileSize
132.01 KB

The dependent patch has been fixed, there's no change to the "for review" one here so just posting a combined one to get a green light from the testbot :)

amateescu’s picture

Status: Needs review » Postponed
FileSize
57.33 KB
132.65 KB
3.55 KB

Rerolled on top of #2336597-84: Convert path aliases to full featured entities and made a few improvements.

amateescu’s picture

Fixed the post update function.

amateescu’s picture

Brought over an improvement from a followup issue.

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.

amateescu’s picture

amateescu’s picture

Title: Convert custom path alias forms to entity forms » [PP-1] Convert custom path alias forms to entity forms
amateescu’s picture

Fixed a few failures, but the JSON:API one needs to be fixed in the parent issue.

amateescu’s picture

amateescu’s picture

amateescu’s picture

Rerolled with the latest patch from the initial conversion issue.

Berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Path/Entity/PathAlias.php
    @@ -50,13 +53,26 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +      ->setRequired(TRUE)
    +      ->addPropertyConstraints('value', [
    +        'Regex' => [
    +          'pattern' => '/^\//i',
    +          'message' => new TranslatableMarkup('The source path has to start with a slash.'),
    +        ],
    +      ])
    +      ->addPropertyConstraints('value', ['ValidPath' => []]);
    

    oh wow, I don't think I've seen that before (the regex).

    But wondering if we could just do that as part of ValidPath?

  2. +++ b/core/lib/Drupal/Core/Path/Plugin/Validation/Constraint/UniquePathAliasConstraintValidator.php
    @@ -0,0 +1,82 @@
    +/**
    + * Constraint validator for a unique path alias.
    + */
    +class UniquePathAliasConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
    +
    +  /**
    +   * The entity type manager.
    +   *
    

    we can't re-use UniqueFieldValueValidator for this I guess?

  3. +++ b/core/modules/path/config/optional/language.content_settings.path_alias.path_alias.yml
    @@ -0,0 +1,8 @@
    +dependencies: {  }
    +id: path_alias.path_alias
    +target_entity_type_id: path_alias
    +target_bundle: path_alias
    +default_langcode: und
    +language_alterable: true
    

    I know from redirect.module that the default language widget options are very confusing for path/redirect and I think we ended up customizing that.

  4. +++ b/core/modules/path/path.module
    @@ -23,20 +28,66 @@ function path_help($route_name, RouteMatchInterface $route_match) {
    +      'settings' => [
    +        'include_locked' => FALSE,
    +      ],
    
    @@ -69,3 +120,33 @@ function path_entity_translation_create(ContentEntityInterface $translation) {
    +    if ($field_name === 'langcode') {
    +      $element['value']['#description'] = t('A path alias set for a specific language will always be used when displaying this page in that language, and takes precedence over path aliases set as <em>- Not specified -</em>.');
    +      $element['value']['#empty_value'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
    +      $element['value']['#empty_option'] = t('- Not specified -');
    +    }
    +
    

    Ah, I see, you did that too :)

  5. +++ b/core/modules/path/path.module
    @@ -69,3 +120,33 @@ function path_entity_translation_create(ContentEntityInterface $translation) {
    +    if ($field_name === 'path') {
    +      $element['value']['#description'] = t('Specify the existing path you wish to alias. For example: /node/28, /forum/1, /taxonomy/term/1.');
    +    }
    +
    +    if ($field_name === 'alias') {
    +      $element['value']['#description'] = t('Specify an alternative path by which this data can be accessed. For example, type "/about" when writing an about page.');
    

    wondering if it wouldn't be cleaner to put this stuff in PathAliasForm, that's how we do it on other entity types like node too?

    And we do use the field description by default, but often these descriptions don't make too much sense as the general field description and vice-versa.

  6. +++ b/core/modules/path/src/Controller/PathController.php
    @@ -99,11 +99,11 @@ public function adminOverview(Request $request) {
             'title' => $this->t('Edit'),
    -        'url' => Url::fromRoute('path.admin_edit', ['pid' => $data->pid], ['query' => $destination]),
    +        'url' => Url::fromRoute('entity.path_alias.edit_form', ['path_alias' => $data->pid], ['query' => $destination]),
           ];
           $operations['delete'] = [
             'title' => $this->t('Delete'),
    -        'url' => Url::fromRoute('path.delete', ['pid' => $data->pid], ['query' => $destination]),
    +        'url' => Url::fromRoute('entity.path_alias.delete_form', ['path_alias' => $data->pid], ['query' => $destination]),
           ];
           $row['data']['operations'] = [
    

    This feels icky, thought we could use list builder operations already or something, but we're refactoring all of this anyway in the list builder issue.

  7. +++ b/core/modules/path/src/Form/PathFilterForm.php
    @@ -65,7 +65,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
        */
       public function resetForm(array &$form, FormStateInterface $form_state) {
    -    $form_state->setRedirect('path.admin_overview');
    +    $form_state->setRedirect('entity.path_alias.collection');
       }
     
    

    makes me wonder if pathauto or redirect use some of these old routes anywhere..

jibran’s picture

Status: Postponed » Needs work
Issue tags: +Needs reroll
amateescu’s picture

Title: [PP-1] Convert custom path alias forms to entity forms » Convert custom path alias forms to entity forms
Issue tags: -Needs reroll

The 'for review' patch doesn't need a reroll, it needs work to address #21.

Sam152’s picture

andypost’s picture

+++ b/core/lib/Drupal/Core/Path/Plugin/Validation/Constraint/UniquePathAliasConstraintValidator.php
@@ -0,0 +1,82 @@
+  private $entityTypeManager;
...
+    $this->entityTypeManager = $entity_type_manager;
...
+    $storage = $this->entityTypeManager->getStorage('path_alias');

Constructor can get storage instead of ETM, also why the property is private?

amateescu’s picture

Status: Needs work » Needs review
Issue tags: +Needs change record updates
FileSize
55.91 KB
3 KB

@Berdir, re #21:

  1. Wondering if we could just do that as part of ValidPath?

    We could, but we still need to do it separately for the alias base field, and IMO it looks better to have these two similar constraints in the same place. It makes the code easier to scan (visually) for someone who needs to debug or something :)

  2. We can't re-use UniqueFieldValueValidator for this I guess?

    We can't because we need to add the language fallback stuff, which is specific to path aliases.

  3. Yup, I scratched my head a bit while doing this.
  4. And of course we ended up with the same overrides :)
  5. Wondering if it wouldn't be cleaner to put this stuff in PathAliasForm, that's how we do it on other entity types like node too?

    And we do use the field description by default, but often these descriptions don't make too much sense as the general field description and vice-versa.

    I don't think it would be cleaner because we are altering widget-level code here. There's no other entity form in core that's altering stuff like $element['<field_name>'][0]['value']..., because that's what hook_field_widget_form_alter() is for :)

  6. Yup, all this goes away in the list builder refactoring patch.
  7. I just checked and we do, so we need to update the change record with these route name changes. Adding the tag for that.

@andypost, no idea why I set that to private, probably an IDE autocomplete mistake. Fixed!

Berdir’s picture

+++ b/core/lib/Drupal/Core/Path/Plugin/Validation/Constraint/UniquePathAliasConstraintValidator.php
@@ -14,20 +14,20 @@
 class UniquePathAliasConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
...
-   * The entity type manager.
+   * The path alias entity storage.
    *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
    */
-  private $entityTypeManager;
+  protected $pathAliasStorage;

private was definitely wrong, not quite so sure about the entity type manager -> storage change. IMHO injecting non-services can be a bit problematic. We already had to add special support for entity storages to \Drupal\Core\DependencyInjection\DependencySerializationTrait for example.

amateescu’s picture

Ok, let's stick with storing only services as class members, I agree that it makes more sense. The interdiff is to #20 :)

andypost’s picture

@berdir as #17 said ...is there existing issue about it? There's both ways used in core now
Personally I prefer to require in constructor only storage cos class has "less knowledge" about external dependencies

amateescu’s picture

@andypost, that's an interesting point, but let's not hold up this patch just for that detail :) Since both ways are used in core, I think we need a policy issue to decide one way or the other, and then we can update everything at once.

After discussing this patch with @Berdir, he mentioned that we should add a BC layer for the routes that we're removing, so here it is.

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

With deprecation tests and everything, beautiful ;)

@andypost: Yeah, I'm happy to argue why I think it's better the inject the entity type manager only in a separate issue (in short, performance when not used and ability to clear storages when certain changes happen. Might not be too relevant for this validator, but matters in other cases).

I think the main point from my review that wasn't addressed is altering the form elements in the widget alter or in the entity form. I prefer doing something like that in the EntityForm class instead of putting things in the .module file, but that's more a personal preference and I don't feel strongly about it. I'll leave that decision to the core maintainer who is going to review this. @amateescu correctly said that we don't do that yet in core in e.g. NodeForm, but we don't have any comparable widget alter hooks either I think, commerce seems to do that a lot though ;) And most core content entity forms unfortunately don't use widgets at all.

I did some manual tests as well and as far as I see, the add/edit forms look identical. There are tiny differences on the delete form (Confirm button becomes Delete, and the confirm message has an extra "the", but that's standardized on entity delete forms and actually seems more correct).

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. We need a change record I think...
    +++ b/core/modules/path/src/Routing/RouteProcessor.php
    @@ -0,0 +1,69 @@
    +    if (in_array($route_name, array_keys($redirected_route_names), TRUE)) {
    +      @trigger_error("The '{$route_name}' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the '{$redirected_route_names[$route_name]}' route instead.", E_USER_DEPRECATED);
    +      static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_names[$route_name]));
    +    }
    

    And the deprecation messages need to link to said change record. Ah I see we are going to update the existing CR - well that CR needs to link to this issue and so do the deprecation messages.

  2. +++ b/core/modules/locale/tests/src/Functional/LocalePathTest.php
    @@ -69,18 +69,18 @@ public function testPathLanguageConfiguration() {
         $edit = [
    -      'source'   => '/node/' . $node->id(),
    -      'alias'    => '/' . $english_path,
    -      'langcode' => 'en',
    +      'path[0][value]' => '/node/' . $node->id(),
    +      'alias[0][value]' => '/' . $english_path,
    +      'langcode[0][value]' => 'en',
         ];
    

    This change is likely to be disruptive but form arrays are not API so this is allowed to change in a minor. Hopefully won't be too painful for contrib and custom tests.

  3. +++ /dev/null
    @@ -1,33 +0,0 @@
    -<?php
    -
    -namespace Drupal\path\Form;
    -
    

    The good thing is that these form class removals would appear not to break contrib - http://grep.xnddx.ru/search?text=Drupal%5Cpath%5CForm&filename=

  4. +++ b/core/modules/path/src/Routing/RouteProcessor.php
    @@ -0,0 +1,69 @@
    +   * Constructs a RestResourceGetRouteProcessorBC object.
    

    This is not a RestResourceGetRouteProcessorBC

  5. +++ b/core/tests/Drupal/FunctionalTests/Hal/PathAliasHalJsonTestBase.php
    @@ -43,14 +43,4 @@ protected function getNormalizedPostEntity() {
    -  /**
    -   * {@inheritdoc}
    -   */
    -  protected function getExpectedCacheContexts() {
    -    return [
    -      'url.site',
    -      'user.permissions',
    -    ];
    -  }
    
    +++ b/core/tests/Drupal/FunctionalTests/Rest/PathAliasResourceTestBase.php
    @@ -109,11 +114,4 @@ protected function getNormalizedPostEntity() {
    -  /**
    -   * {@inheritdoc}
    -   */
    -  protected function getExpectedCacheContexts() {
    -    return ['user.permissions'];
    -  }
    

    First reaction is that this seems unrelated - how come these changes are necessary?

amateescu’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
61.26 KB
5.1 KB

Thanks for the review!

First reaction is that this seems unrelated - how come these changes are necessary?

Those changes were needed because I added the path module in PathAliasResourceTestBase, but that was a mistake, we don't need it there, so I reverted those three hunks from the patch :)

Fixed all the other points as well.

alexpott’s picture

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.

amateescu’s picture

Issue summary: View changes

Updated the issue summary to list all the changes to form classes / IDs as well as deprecated routes and their replacements.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Discussed with @catch and @plach and consider whether we should put this in as a breaking change in 9.0.x but they both felt it was better to go in 8.8.x and I agreed.

Committed f9528c5 and pushed to 9.0.x and cherry-picked to 8.9.x and 8.8.x. Thanks!

  • alexpott committed f9528c5 on 9.0.x
    Issue #3007832 by amateescu, Berdir, andypost, alexpott: Convert custom...

  • alexpott committed 4d4484b on 8.8.x
    Issue #3007832 by amateescu, Berdir, andypost, alexpott: Convert custom...

  • alexpott committed b384bc0 on 8.9.x
    Issue #3007832 by amateescu, Berdir, andypost, alexpott: Convert custom...
amateescu’s picture

Thanks, @alexpott!

Updated the CR and added a release note snippet.

amateescu’s picture

Issue summary: View changes
jibran’s picture

Found this gold #147143: Add new URL aliases UI and moved it to 9.1.x

xjm credited pameeela.

xjm’s picture

Updating the release note with disruption information from @pameeela, and crediting her for the improvement

xjm’s picture

Issue summary: View changes

And actually pasting the draft snippet.

amateescu’s picture

The update looks great to me, thanks!

Status: Fixed » Closed (fixed)

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