Problem/Motivation

While working on #2565931: Handle long comment bundle names I discovered that the migrate lookup service throws a PluginNotFoundException with the default message which assumes the plugin_id is a string but for migratelookup it can be an array. This results in the error

1) Drupal\Tests\migrate_drupal_ui\Functional\d7\DoubleSlashTest::testMigrateUpgradeExecute
Exception: Notice: Array to string conversion
Drupal\Component\Plugin\Exception\PluginNotFoundException->__construct()() (Line: 22)

Steps to reproduce

Proposed resolution

Use a custom message in the migratelookup service when the PluginNotFoundException is thrown.

Remaining tasks

Patch
Review
Commit

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

quietone created an issue. See original summary.

quietone’s picture

Status: Active » Needs review
FileSize
2.66 KB

And here is a start. The custom message is just a copy of what is used in PluginNotFoundException and I think it needs improvement. Any suggestions?

quietone’s picture

Fix typos is title

quietone’s picture

Title: Use a custom error message for PlulginNotFoudnException in the migratelookup service » Use a custom error message for PluginNotFoundException in the migratelookup service

Hmm, my changes were not saved. Try again.

benjifisher’s picture

Status: Needs review » Needs work

Instead of

      $migrations = print_r($migration_ids, TRUE);
      throw new PluginNotFoundException($migration_ids, sprintf("Plugin ID '%s' was not found.", $migrations));

how about something like this?

      $migrations = implode(', ', (array) $migration_ids);
      throw new PluginNotFoundException($migration_ids, sprintf("Plugin IDs '%s' were not found.", $migrations));

Variant:

      $message = is_array($migration_ids)
        ? "Plugin IDs '" . implode(', ', $migration_ids) . "' were not found."
        : '';
      throw new PluginNotFoundException($migration_ids, $message);

We might even use "', '" as the first argument to implode(), leading to an error message like

Plugin IDs 'foo', 'bar' were not found.

Should we consider making that change in the PluginNotFoundException constructor? How common is this pattern of looking up one or several plugins by ID?

I searched the core migration modules for "PluginNotFoundException" trying to answer that question. It is too close to my bed time for me to create any new issues, but here are some notes:

  1. We have kernel and unit tests for exceptions thrown by MigrateLookup::lookup() and MigrateStub::createStub() that are almost identical. Can we remove the kernel tests?
  2. MigrateFieldPluginManager::getPluginIdFromFieldType() copies the doc block from its interface, with a few extra lines, instead of using {@inheritdoc} and regular comments.

I did not find any other examples of the one-or-several (string or string[]) pattern, so maybe changing the PluginNotFoundException constructor is not a great idea.

quietone’s picture

Status: Needs work » Needs review
FileSize
2.69 KB

I agree that making a change in the constructor of PluginNotFoundException is not the way forward. I think the best we can do is what you have suggested which I have implemented in this patch. I also added array keys to the arrays in the data provider. No interdiff because the patch is so small.

1. I have looked at MigrateLookup tests and the MigrateStub tests and agree that the Kernel test is not adding any additional testing. I agree it is not needed and that the Unit tests are sufficient.

2. Yes, that needs some cleanup.

While it isn't late for me I have other plans for the day and can't make new issue right now.

benjifisher’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

I created #3187794: Remove redundant kernel tests in the Migrate module and supplied a patch. I was going to add an issue for #5.2, but first I checked when the doc block was added. It was discussed in #3004456-25: Add a weighting option to MigrateField plugin's definition and lower weight of deprecated 'date' plugin so it isn't used and the following comment. I guess that implementation details should not be added to the interface, and moving some lines from the doc block to regular comments (including one @see comment) would mean that they no longer show up in the API docs.

Back to this issue!

The testbot approves, the code looks good, and the exception messages are a lot easier to read than in the original patch. RTBC

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Needs a reroll.

quietone’s picture

Status: Needs work » Needs review
FileSize
287 bytes
2.77 KB

And the reroll, it was straightforward.

benjifisher’s picture

Status: Needs review » Reviewed & tested by the community

Sorry, I added that line in #3187794: Remove redundant kernel tests in the Migrate module. The earlier patches on this issue already asserted the exception message supplied by the data provider. The reroll removes the assertion of a static exception message.

Back to RTBC.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/migrate/tests/src/Unit/MigrateLookupTest.php
@@ -48,14 +48,36 @@ public function testLookup() {
+    return [
+      'string' => [
+        'bad_plugin',
+        "Plugin ID 'bad_plugin' was not found.",
+      ],
+      'array one item' => [
+        ['bad_plugin'],
+        "Plugin IDs 'bad_plugin' were not found.",
+      ],

Should we do:

 is_array($migration_ids) && count($migration_ids) > 1

when determining the message, so that the array of one item gets treated as singular?

quietone’s picture

If you like. We still need to convert the array to a string when there is only one item in the array.

benjifisher’s picture

Status: Needs review » Needs work

This is still problematic. The constructor for PluginNotFoundException ignores the $plugin_id parameter if a non-empty message is given, so the patches here prevent an error. But the @param comment specifies that $plugin_id is a string. Sooner or later, we will enforce that with a type declaration. The patch in #12 replaces a one-element array with a string, but still passes an array when there are multiple plugin IDs.

Also, $migration_ids[0] will not work if $migration_ids = ['a' => 'my_migration']. It is safer to use reset().

I was going to say that we could throw one exception for each migration ID on the list, but that does not work.

We could create a chain of exceptions:

    if (!$migrations) {
      $exception = NULL;
      foreach ((array) $migration_ids as $migration_id) {
        $exception = new PluginNotFoundException($migration_id, '', 0, $exception);
      }
      throw $exception ?? new PluginException('No plugin IDs were provided.');
    }

I cannot think of a more specific exception to throw when $migration_ids == [].

The alternative is to come up with a single string to pass to the PluginNotFoundException constructor as $plugin_id
as well as a string to pass as $message.

On second thought, I think the best solution, respecting the semantics of PluginNotFoundException, is to throw the more generic PluginException whenever $migration_ids is an array and count($migration_ids) != 1. When we throw PluginException, we just have to come up with a message; we do not have to make up a single plugin ID.

quietone’s picture

Status: Needs work » Needs review
FileSize
3.34 KB
3.85 KB

@benjifisher, All good points!

Here is another attempt, implementing the solution in the final paragraph.

benjifisher’s picture

Status: Needs review » Needs work

This looks like it will break in the case of an empty array. We certainly want test coverage for that case.

Can we add test coverage for the case of an empty array?

anmolgoyal74’s picture

Status: Needs work » Needs review
FileSize
3.94 KB
468 bytes

Added tests for an empty array.

benjifisher’s picture

Status: Needs review » Needs work

@quietone: I meant to mention that I like the way you implemented the logic in #14. It is very clear and efficient.

@anmolgoyal74: Thanks for adding the test!

I am sorry I did not notice this in my earlier review:

+++ b/core/modules/migrate/tests/src/Unit/MigrateLookupTest.php
@@ -47,15 +48,63 @@ public function testLookup() {
...
+  public function testExceptionOnMultipleMigrationsNotFound($migrations, $message) {
...
+  /**
+   * Provides data for testExceptionOnMigrationsNotFound.
+   */
+  public function providerExceptionOnMultipleMigrationsNotFound() {

The method name in the comment does not match the name of the test method. It is a small thing, but we may as well fix it.

quietone’s picture

Status: Needs work » Needs review
FileSize
624 bytes
3.95 KB

@benjifisher, the thanks goes to your reviews, because of previous reviews I too extra time to find the 'best' way to do that bit. Thanks!

Ah, I did miss one comment fix. All fixed now.

benjifisher’s picture

Status: Needs review » Reviewed & tested by the community

While we are at it, we should also thank @catch for making us take another look at this issue.

The last nit-pick is resolved ... the last nit is picked? ... so back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/migrate/src/MigrateLookup.php
@@ -35,6 +36,12 @@ public function lookup($migration_id, array $source_id_values) {
+          throw new PluginException("Plugin IDs '" . implode(', ', $migration_id) . "' were not found.");

+++ b/core/modules/migrate/tests/src/Unit/MigrateLookupTest.php
@@ -47,15 +48,63 @@ public function testLookup() {
+        "Plugin IDs 'foo, bar' were not found.",

I think this should
Plugin IDs 'foo', 'bar' were not found.

We can use "', '" as the glue.

The reason being that usual quotes are used to quote a single thing - not multiple things.

quietone’s picture

Status: Needs work » Needs review
FileSize
1.07 KB
3.95 KB

@alexpott, thanks.

This patch changes the glue as suggested in #20.

benjifisher’s picture

Status: Needs review » Reviewed & tested by the community

The patch in #21 makes the improvement requested in #20. Back to RTBC.

alexpott’s picture

Version: 9.2.x-dev » 9.1.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed c0904f72e1 to 9.2.x and dd85517943 to 9.1.x. Thanks!

  • alexpott committed c0904f7 on 9.2.x
    Issue #3187386 by quietone, anmolgoyal74, benjifisher, alexpott, catch:...

  • alexpott committed dd85517 on 9.1.x
    Issue #3187386 by quietone, anmolgoyal74, benjifisher, alexpott, catch:...

Status: Fixed » Closed (fixed)

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