Problem/Motivation

Right now, pathauto pattern migration is a monolithic migration, and this can cause issues with the migrated pathauto pattern config entities:
even though patterns with bundle selection criteria depend on the corresponding entity bundle migrations, this isn't reflected in the migration dependencies. Right now, only d7_node_type is defined as a dependency, but e.g. taxonomy term pattern migrations require d7_taxonomy_vocabulary.

Proposed resolution

Derive pathauto pattern migrations per entity type (and bundle), and set the accurate migration dependencies (for known destination entity types) in the new deriver class.

Remaining tasks

I wasn't able to properly separate this issue from #3179835: Migrate forum pattern to taxonomy term forums if forum is enabled on the source site which prepares the required components for this issue. So for now:

User interface changes

Nothing.

API changes

Nothing.

Data model changes

Accurate migration dependencies for pathauto pattern migrations.

CommentFileSizeAuthor
#25 interdiff-3179865-24-25.txt28.42 KBhuzooka
#25 pathauto-derive_pathauto_migrations_per_entity_type-3179865-25.patch40.09 KBhuzooka
#24 interdiff-3179865-22-24.txt546 byteshuzooka
#24 pathauto-derive_pathauto_migrations_per_entity_type-3179865-24--combined-with-3179835.patch47.55 KBhuzooka
#24 pathauto-derive_pathauto_migrations_per_entity_type-3179865-24.patch13.34 KBhuzooka
#22 interdiff-3179865-18-22.txt2.67 KBhuzooka
#22 pathauto-derive_pathauto_migrations_per_entity_type-3179865-22--combined-with-3179835.patch47.54 KBhuzooka
#22 pathauto-derive_pathauto_migrations_per_entity_type-3179865-22.patch13.33 KBhuzooka
#18 interdiff-3179865-13-18.txt7.83 KBhuzooka
#18 pathauto-derive_pathauto_migrations_per_entity_type-3179865-18--combined-with-3179835.patch46.79 KBhuzooka
#18 pathauto-derive_pathauto_migrations_per_entity_type-3179865-18.patch12.58 KBhuzooka
#13 interdiff-3179865-10-13.txt1.59 KBhuzooka
#13 pathauto-derive_pathauto_migrations_per_entity_type-3179865-13--combined-with-3179835.patch44.37 KBhuzooka
#13 pathauto-derive_pathauto_migrations_per_entity_type-3179865-13.patch8.5 KBhuzooka
#10 interdiff-3179865-6-10.txt1.97 KBhuzooka
#10 pathauto-derive_pathauto_migrations_per_entity_type-3179865-10--combined-with-3179835.patch44.22 KBhuzooka
#10 pathauto-derive_pathauto_migrations_per_entity_type-3179865-10.patch8.35 KBhuzooka
#5 interdiff-3179865-3-5.txt4.47 KBhuzooka
#5 pathauto-derive_pathauto_migrations_per_entity_type-3179865-5--combined-with-3179835.patch42.91 KBhuzooka
#5 pathauto-derive_pathauto_migrations_per_entity_type-3179865-5.patch8.73 KBhuzooka
#3 interdiff-3179865-2-3.txt2.05 KBhuzooka
#3 pathauto-derive_pathauto_migrations_per_entity_type-3179865-3--combined-with-3179835.patch42.3 KBhuzooka
#3 pathauto-derive_pathauto_migrations_per_entity_type-3179865-3.patch7.96 KBhuzooka
#2 pathauto-derive_pathauto_migrations_per_entity_type-3179865-2--combined-with-3179835.patch42.29 KBhuzooka
#2 pathauto-derive_pathauto_migrations_per_entity_type-3179865-2.patch7.96 KBhuzooka
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

huzooka created an issue. See original summary.

huzooka’s picture

huzooka’s picture

huzooka’s picture

Wim Leers’s picture

  1. +++ b/src/Plugin/migrate/PathautoPatternDeriver.php
    @@ -0,0 +1,86 @@
    +        $derivative_id = $bundle
    +          ? implode(PluginBase::DERIVATIVE_SEPARATOR, [
    +            $entity_type,
    +            $bundle,
    +          ])
    +          : "{$entity_type}_default";
    

    🤔 So when no *_default as a derivative ID. Sounds reasonable!

    OTOH … why even rename it in this case? Why not keep the original plugin ID? Isn't there only ever going to be one?

  2. +++ b/src/Plugin/migrate/PathautoPatternDeriver.php
    @@ -0,0 +1,86 @@
    +        if ($bundle) {
    +          switch ($entity_type) {
    +            case 'node':
    +              $migration_requirements = ['d7_node_type'];
    +              break;
    +
    +            case 'taxonomy_term':
    +              $migration_requirements = ['d7_taxonomy_vocabulary'];
    +              break;
    +          }
    +        }
    

    Let's add default: and add explicit logging?

    That way the person doing the migration would be warned about missing dependencies (for bundle definitions for entity types whose bundle creation migration is not known). It could even check \Drupal\Core\Entity\EntityTypeInterface::getBundleEntityType() to not show this message when bundles are not configurable but hardcoded.

  3. +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -86,6 +93,38 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +      // Fetch all pattern variables whose value is not a serialized empty
    +      // string.
    +      $query->condition('name', 'pathauto_%_pattern', 'LIKE');
    

    I don't think this comment matches the query? 😅

  4. +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -86,6 +93,38 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +      // This is the default condition.
    +      $condition = $query->orConditionGroup();
    

    🤔 What does this mean? You're OR'ing with the default condition I think?

  5. +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -86,6 +93,38 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +      // Source variable IDs can be mapped to the same "entity_type" and
    

    🤔 What is a "source variable ID"?

  6. +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -86,6 +93,38 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +      // "bundle". At the end, this can cause that there are multiple patterns
    +      // for the same entity type and bundle.
    

    🤯 Woah! How? Can you include an example in this comment? 🙏

  7. +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -123,9 +162,13 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +        // If entity type was undeterminable, skip this row.
    +        if (empty($row['entity_type'])) {
    +          continue;
    +        }
    

    When/how could this happen? Including an example here might be valuable 🙏

  8. +++ b/tests/src/Kernel/Migrate/d7/MigratePathautoTest.php
    @@ -83,6 +81,11 @@ class MigratePathautoTest extends MigrateDrupal7TestBase {
    +      'd7_pathauto_patterns:taxonomy_term',
    +      'd7_pathauto_patterns:taxonomy_term_default',
    

    Why do these two happen in this order?

  9. +++ b/tests/src/Kernel/Migrate/d7/MigratePathautoTest.php
    @@ -92,6 +95,11 @@ class MigratePathautoTest extends MigrateDrupal7TestBase {
    +    // Execute the rest of the migrations. For now, this is equal with
    

    🤓 s/with/to/

huzooka’s picture

Assigned: Unassigned » huzooka
huzooka’s picture

Re #7 (it is the review of #5, and not the most-recent path posted at #6):

  1. Well, if we found a fallback pattern (which isn't restricted to a specific entity bundle), it does not mean that there aren't bundle-specific patterns. We cannot have both d7_pathauto_pattern:node and d7_pathauto_pattern:node:article because in this case the plugin manager cannot decide what d7_pathauto_pattern:node refers to: it is also the ID of the fallback pattern and also the partial ID of the bundle-specific pattern migration derivatives (d7_pathauto_pattern:node:<whatever>).
  2. Drupal 7 Pathauto does not support other bundleable entity types. There are only node and taxonomy_term.
  3. Todo Fixed in #10.
  4. Obsolete, does not exists in #6.
  5. Obsolete, does not exists in #6.
  6. Obsolete, does not exists in #6. This was referring to the forum pattern. See #3179835-13: Migrate forum pattern to taxonomy term forums if forum is enabled on the source site.
  7. Todo (it needs additional comments) Fixed in #10.
  8. There is no need for this order. But why should that happen in a reversed order?..
  9. Todo Fixed in #10.
huzooka’s picture

Wim Leers’s picture

#9

  1. 👍 That's clear, thanks!
  2. 🙏 Aha! That makes sense. Then let's A) document this explicitly, B) enforce this — and we can do both simultaneously by adding
      default:
        throw new \LogicException('pathauto in Drupal 7 only supports bundle-specific patterns for nodes and taxonomy terms.');
    

    Defensive programming FTW 😊

  3. 👍

#9.8: it just looks odd to me to first import :foo and then :foo_default. It's still not entirely clear to me what the difference is.

Review

  1. 🤓
    +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -31,6 +48,26 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +        throw new \LogicException(sprintf('If "bundle" configuration is set for %s migration source plugin, "entity_type" configuration has also be defined.', get_class($this)));
    

    s/has also be/must also be/

  2. 🤓
    +++ b/src/Plugin/migrate/source/PathautoPattern.php
    @@ -52,8 +52,7 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
    +      // Fetch every pattern variables.
    

    s/every pattern variables/every pattern variable/

Wim Leers’s picture

RE #9.8: @huzooka clarified that d7_pathauto_patterns:taxonomy_term results in all bundle-specific derivatives getting executed, so for example:

  • d7_pathauto_patterns:taxonomy_term:tags
  • d7_pathauto_patterns:taxonomy_term:sujet_de_discussion

And only after those d7_pathauto_patterns:taxonomy_term_default gets migrated. This is the fallback pattern. So … it totally makes sense 👍

Wim Leers’s picture

RTBC once the blocker lands 🚢

Wim Leers’s picture

I'm tempted to mark this a bug instead of a feature request — the issue summary describes pretty clearly why this is a bug.

Wim Leers’s picture

Status: Postponed » Needs work

Problem

+++ b/src/Plugin/migrate/PathautoPatternDeriver.php
@@ -0,0 +1,89 @@
+        if ($bundle) {
+          switch ($entity_type) {
+            case 'node':
+              $migration_requirements = ['d7_node_type'];
+              break;
+
+            case 'taxonomy_term':
+              $migration_requirements = ['d7_taxonomy_vocabulary'];
+              break;
+
+            default:
+              throw new \LogicException('Drupal 7 Pathauto only supports bundle-specific patterns for nodes and taxonomy terms.');
+          }
+        }

When the source site has installed file_entity, they'll also have bundles for the file entity type.

On a particular site I am testing against, I'm seeing the following Pathauto patterns in the variable table:

  • pathauto_file_audio_pattern
  • pathauto_file_document_pattern
  • pathauto_file_image_pattern
  • pathauto_file_pattern
  • pathauto_file_video_pattern

which results in this error:

LogicException: Drupal 7 Pathauto only supports bundle-specific patterns for nodes and taxonomy terms. in Drupal\pathauto\Plugin\migrate\PathautoPatternDeriver->getDerivativeDefinitions() (line 75 of modules/pathauto/src/Plugin/migrate/PathautoPatternDeriver.php).
…
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
The website encountered an unexpected error. Please try again later.

Proposals

1️⃣ First, to make this easier to debug, this should at the very least be throwing a more helpful exception, like so:

-              throw new \LogicException('Drupal 7 Pathauto only supports bundle-specific patterns for nodes and taxonomy terms.');
+              var_dump($source);
+              throw new \LogicException(sprintf('Drupal 7 Pathauto only supports bundle-specific patterns for nodes and taxonomy terms. Now observed %s bundle for %s entity type.', $bundle, $entity_type));

2️⃣ Second, it should not crash, but instead it should just continue without adding dependencies.

3️⃣ Third, let's add explicit support for file entities?

huzooka’s picture

Assigned: Unassigned » huzooka

Well, Pathauto Entity module is the thing which makes it possible to have pathauto pattern(s) for file entities (and any kind of (custom) entities – and even for comments).

huzooka’s picture

Wim Leers’s picture

Manually tested #18. I can confirm it works.

Review

+++ b/src/Plugin/migrate/PathautoPatternDeriver.php
@@ -48,6 +90,13 @@ class PathautoPatternDeriver extends DeriverBase {
+        if (
+          !$pathauto_entity_installed_on_source &&
+          !in_array($entity_type, ['node', 'taxonomy_term', 'user'], TRUE)
+        ) {
+          continue;
+        }

Isn't this returning too early? Doesn't this mean that the new file and comment cases you added later in this file will never get executed?

huzooka’s picture

Re #19: Those patterns work only if you have Pathauto Entity installed on the source site. So I think it isn't a too-early-return.

huzooka’s picture

#20 isn't totally correct.

huzooka’s picture

This patch addresses the concern raised by #19.

Summary:
D7 File Entity provides support for file pathauto pattern. This means that file entities may have a bundle-agnostic (aka default) pattern.
D7 Pathauto Entity goes a bit further and it also supports per-bundle file patterns (while a default pattern can be still functional and behave as a fallback).

huzooka’s picture

Assigned: Unassigned » huzooka
Status: Needs review » Needs work

The patch #22 is wrong.

I've added a continue inside the switch statement (see the interdiff) – and inside a switch, it is equivalent to a break. I should use continue 2 instead.

huzooka’s picture

huzooka’s picture

The source plugin should support returning only the default patterns (eg. pathauto_node_pattern).
This patch:

  • Adds support for returning only the default patterns. If you specify the bundle config as FALSE, then the source plugin returns the default pattern of the given entity_type.
  • Adds a kernel test for the source plugin.

Postponing on #3179835: Migrate forum pattern to taxonomy term forums if forum is enabled on the source site.

Wim Leers’s picture

Looks good 👍

+++ b/src/Plugin/migrate/source/PathautoPattern.php
@@ -164,6 +173,13 @@ class PathautoPattern extends DrupalSqlBase implements ContainerFactoryPluginInt
+      // If the current 'bundle' config is FALSE, then we want to skip results
+      // which have bundle value, because this derivative should only migrate
+      // the entity type's default (fallback) pattern.
+      if ($this->configuration['bundle'] === FALSE && !empty($row['bundle'])) {

🤓 Nit.

So this means we assign different meanings to bundle === NULL (the default value, pre-existing) and bundle === FALSE (introduced here).

We should document that in \Drupal\pathauto\Plugin\migrate\source\PathautoPattern::__construct() IMHO, or better yet, in the class-level docblock.

DamienMcKenna’s picture

Could someone please clarify this part:

+      // Add the original simpletest prefix so SQLite can attach its database.
+      // @see \Drupal\Core\Database\Driver\sqlite\Connection::init()
+      $connection_info[$target]['prefix'][$value['prefix']['default']] = $value['prefix']['default'];

That class method doesn't exist and this change causes the tests to fail with the error "Illegal string offset 'default'".

DamienMcKenna’s picture

Correction, it fails on this bit:

      // Simpletest uses 7 character prefixes at most so this can't cause
      // collisions.
      $connection_info[$target]['prefix']['default'] = $prefix . '0';

$connection_info[$target]['prefix'] is a string, not an array.

This change lets the tests pass:

diff --git a/tests/src/Kernel/Plugin/migrate/source/PathautoSourceTestBase.php b/tests/src/Kernel/Plugin/migrate/source/PathautoSourceTestBase.php
index 89d4a60..3a8c402 100644
--- a/tests/src/Kernel/Plugin/migrate/source/PathautoSourceTestBase.php
+++ b/tests/src/Kernel/Plugin/migrate/source/PathautoSourceTestBase.php
@@ -85,11 +85,11 @@ private function createMigrationConnection() {
       $prefix = is_array($value['prefix']) ? $value['prefix']['default'] : $value['prefix'];
       // Simpletest uses 7 character prefixes at most so this can't cause
       // collisions.
-      $connection_info[$target]['prefix']['default'] = $prefix . '0';
+      $connection_info[$target]['prefix'] = $prefix . '0';
 
       // Add the original simpletest prefix so SQLite can attach its database.
       // @see \Drupal\Core\Database\Driver\sqlite\Connection::init()
-      $connection_info[$target]['prefix'][$value['prefix']['default']] = $value['prefix']['default'];
+      // $connection_info[$target]['prefix'][$value['prefix']['default']] = $value['prefix']['default'];
     }
     Database::addConnectionInfo('migrate', 'default', $connection_info['default']);
   }
DamienMcKenna’s picture

Looking at it again I spotted this:

  /**
   * Changes the database connection to the prefixed one.
   *
   * @see \Drupal\Tests\migrate\Kernel\MigrateTestBase::createMigrationConnection()
   *
   * @todo Refactor when core doesn't use global.
   *   https://www.drupal.org/node/2552791
   */
  private function createMigrationConnection() {

#2552791 was closed as a duplicate of #2681869 which was committed in 2016. Does this mean that other changes are needed in core, or that the code needs to be refactored to account for core changes from 2016? The comment isn't clear.

DamienMcKenna’s picture

Status: Postponed » Needs work

Putting this back to "needs work" due to my findings above.