Problem/Motivation

In Drupal\migrate\Plugin\Migration, the variable $migration_dependencies is an array with two keys: 'required' and 'optional'. Each of $migration_dependencies['required'] and $migration_dependencies['optional'] is an indexed array (list) of migration IDs.

Sometimes, but not always, each of the two lists is "expanded" so that it includes all derivatives of any migration on the list. Specifically,

  • When a migration is created by Drupal\migrate\Plugin\MigrationPluginManager::createInstances(), each of the two lists is expanded.
  • When a migration is altered by Drupal\migrate\Plugin\Migration::set('migration_dependencies', ...), the lists are not expanded. The calling function can set $migration_dependencies to any value.

Proposed resolution

Short version: validate on set, expand on get.

In the following list, $migration_dependencies can be "set" in the constructor or using the set() method.

  1. When the variable is set, ensure that it is an array of arrays, with the expected outer keys. Previously, this check was in getMigrationDependencies().
  2. Add the optional, boolean parameter $expand to getMigrationDependencies(). The default value is FALSE, corresponding to the current behavior.
  3. Deprecate getMigrationDependencies() without setting the new parameter to TRUE.
  4. When $expand is set, "expand" the lists. Previously, the lists were expanded when $migration_dependencies is set by createInstances(), but not in set().
  5. The method expandPluginIds() in the plugin manager is now public so that it can be called from the Migration class.

Changes to the tests

When expanding the lists of plugin IDs, getMigrationDependencies() now checks with the plugin manager that the IDs are valid. (The check is done in expandPluginIds().) Now, mock the plugin manager in the unit tests. For the functional tests, add a test module that provides several migration plugins.

Expect a deprecation when setting invalid dependencies, not when getting them.

The constructor of the TestMigration class, like the "real" constructor, ensures that $migration_dependencies has at least the expected keys. Previously, this was done in getMigrationDependencies().

Remaining tasks

User interface changes

None

API changes

In Drupal\migrate\Plugin\Migration, deprecate getMigrationDependencies(FALSE). Do NOT add the new parameter to the interface.

In Drupal\migrate\Plugin\MigrationPluginManager, make expandPluginIds() public and add it to the interface.

Release notes snippet

Calling Drupal\migrate\Plugin\Migration::getMigrationDependencies() without expanding the dependencies is deprecated. In most cases, use Drupal\migrate\Plugin\Migration::getMigrationDependencies(TRUE). For more information, see the change record Migration::getMigrationDependencies() is deprecated.

Issue fork drupal-3262395

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

benjifisher created an issue. See original summary.

benjifisher’s picture

Personally, I prefer the expand-on-access strategy (#2 from the current Proposed resolution section of the issue summary). This is already the approach used in #2796755: [PP-1] Deprecate Migration::set() starting with the patch in #130.

Perhaps I am being over-cautious, but I am worried that a contrib or custom module might do something like this:

$migration = $plugin_manager->createInstance(...);
// Now $migration->migration_dependencies is expanded.
$migration->set('migration_dependencies', ...);
// Now it can be anything.
$dependencies = $migration->getMigrationDependencies();
// Will the code that follows break if we change $dependencies?

So I think the safest thing to do is

  1. Deprecate getMigrationDependencies() and do not change its return value.
  2. Add a new method, getExpandedDependencies(), that applies expandPluginIds() to the return value but does not change the class variable.
  3. When we remove getMigrationDependencies(), go to a strict expand-on-access strategy.

For (2), other possible names are getDerivedDependencies() or getAllDependenciesIncludingDerivatives(). (shrug)

For (3), I mean we can update createInstances() so that it does not apply expandPluginIds().

As @alexpott mentioned in #2796755: [PP-1] Deprecate Migration::set(), getExpandedDependencies() is idempotent. That is, it is safe to apply it more than once (when setting the variable, when changing it, and when accessing it). Safe, but a little wasteful.

benjifisher’s picture

How should we handle expandPluginIds()?

In #2796755: [PP-1] Deprecate Migration::set(), we make that method public, so that it can be used from both MigrationPluginManager and Migration. In #146 on that issue, @jibran pointed out that this is a BC break. Perhaps we should update the policy to consider the plugin manager internal: #3262252: Add concrete plugin managers to the list of things considered internal.

If we want to keep the method protected, then one option is to add a trait with the method addDerivatives(array $migration_ids, MigrationPluginManagerInterface $manager). Then expandPluginIds() can be implemented as addDerivatives(..., $this) and getExpandedDependencies() can call addDerivatives(..., $this->migrationPluginManager).

A variant of that idea is for the trait to define expandPluginIds(array $migration_ids, ?MigrationPluginManagerInterface $manager). If $manager is empty, then replace it with $this. Then we do not need the wrapper function and we do not have to decide on a name for the new method. ;)

Am I over engineering?

Currently, createInstances() uses expandPluginIds() twice. The first call generates the list of migrations to instantiate. The second call expands the dependencies of each instantiated migration plugin. In #2, I suggested eventually removing the second call, but we will still need the first one. So we need to keep some form of expandPluginIds().

benjifisher’s picture

Status: Active » Needs work

Ouch, that is a lot of test failures!

I thought I set the status to NR, but I guess it should now be NW until I get the tests to pass.

benjifisher’s picture

I think the latest commits will fix a lot of the failing tests.

Still NW for a deprecation test and an IS update.

Also, I think that adding an optional parameter to expandPluginIds() is too "clever". I want to use addDerivatives() as in #3.

benjifisher’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update, -Needs tests +Needs change record

Yesterday's commits fixed all the failing tests: better than I expected.

Today's commits add a deprecation test and added addDerivatives(): see #6 and the extended commit message. I am removing the issue tag for tests.

I am updating the issue summary, so I am removing that issue tag. Unless the testbot disagrees, I think this issue is ready for review.

If the tests pass, then I will add a change record first thing tomorrow.

benjifisher’s picture

Issue summary: View changes
Issue tags: -Needs change record

I added a draft change record, so I am removing the issue tag.

quietone’s picture

I still owe this issue a more thorough review. My comments assume that the approach here is what will be done. Because of that I am not setting this is Needs Work.

benjifisher’s picture

I made some of the changes requested in the MR comments.

Re the approach: I guess the question is whether to expand dependencies when setting or when getting.

Given the work on the parent issue, I want to make it easy to implement the new add*Dependencies() methods. Once we deprecate and remove set() and getMigrationDependencies(), we will have the constructor and the two add* methods that change $migration_dependencies and one method that gets the value.

I think it is simpler to have the "expand" code in the one getter, instead of the three setters.

I am not sure, but I think it is also more efficient. Often, we will set once and get once. There are places we might set, add, add, and get. Are there any places where we will get several times after setting?

quietone’s picture

One thing I don't see is a test with a deriver and following from that we can't have a fail patch.

quietone’s picture

Status: Needs review » Needs work

Setting to NW for the unresolved issue and the question in #11.

benjifisher credited heddn.

benjifisher’s picture

Status: Needs work » Needs review

I think I have addressed all the comments on the MR, so back to NR.

I am adding credit to @heddn for some of those comments.

I have not yet incorporated the changes discussed on the MR into the issue summary.

quietone’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs change record updates

Hmm, that is interesting my comments from 28 April 2022 at 12:58 appear before the comment by benjifisher on 21 April 2022 at 16:31.

I reviewed the changes made since my last review of the MR. It all looks good to me but on a second pass I thought the CR needs an update.

The MR itself is fine, and we have taken it as far as we can. The addition of the trait, \Drupal\Component\Plugin\PluginManagerTrait, needs to be accepted. So, I am setting this to RTBC to get more eyes on it.

benjifisher’s picture

Issue summary: View changes
Issue tags: -Needs change record updates

I updated the change record, so I am removing that issue tag.

The change record has details about the deprecation messages and exceptions, but the issue summary does not. I am updating the issue summary a little, less than I anticipated in #14.

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.

benjifisher’s picture

Version: 9.5.x-dev » 10.1.x-dev

I believe that new deprecations have to target 10.1.x, so I rebased the feature branch on 10.1.x and updated the MR.

I also updated the deprecation messages: change 9.4.0 to 10.1.0 and refer to the CR for this issue, not the parent.

benjifisher’s picture

Status: Reviewed & tested by the community » Needs review
quietone’s picture

Status: Needs review » Reviewed & tested by the community

I reread the CR, which looks great. And reviewed the commits made since my last review. They were changes to the version in the deprecation message and the link to the CR. Those changes are correct.

I checked the minutes of the last two migrate meetings (this issue was mentioned at those meetings), to see if there was anything specific check. There was not.

So, all good to go!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I'm not sure about the new trait. I think we should consider making \Drupal\migrate\Plugin\MigrationPluginManager::expandPluginIds() public. The trait is odd and this feels a better way to solve the problem. We can add the method to \Drupal\migrate\Plugin\MigrationPluginManagerInterface under the 1-to-1 interface to class and the fact that any custom migration manager is going to need to extend the base class anyway really. See the example in core - \Drupal\migrate_drupal\MigrationPluginManager

benjifisher’s picture

@alexpott, thanks for looking at this issue.

There are a couple of reasons for introducing the new trait, but none of them are really strong. I did not want to make the method public for BC reasons, but #21 points out that it is OK in this case. It seems to me that the method has nothing to do with migrations, so it makes sense to move it to the Drupal\Component\Plugin namespace. Then there is the vague hope that, once we have the trait, we might find other uses for addDerivatives() and/or we might find other methods that can be added to the trait.

I will make the suggested changes as soon as I can find some time.

alexpott’s picture

We can still refactor the method into a trait later when we have a use case to do that. At the moment we don't have that use-case so this feels premature to me.

benjifisher’s picture

Status: Needs work » Needs review

I replied to the comments on the MR and updated it as follows:

  1. Rebase, resolving add/add conflicts caused by #3198608: trackLastImported YAML key to enable tracking the last import date of a row is undocumented and confusing..
  2. Remove the new trait. Instead, make expandPluginIds() public (#21).
  3. Call MigrationPluginManager::getDefinitions() outside the loop.

The second change simplifies the array_map() in Migration::getExpandedDependencies(). It also simplifies MigrationTest, since only one method from the plugin manager has to be mocked, not two.

Back to NR.

benjifisher’s picture

Issue summary: View changes

I am adding one commit based on the latest comment on the MR.

I am also updating the proposed resolution for the changes I made yesterday. I will update the change record next.

In #21 and on the MR, @alexpott asked whether we considered making expandPluginIds() public. Reviewing the earlier comments on this issue, I am reminded that we did this on the parent issue, and #2796755-146: [PP-1] Deprecate Migration::set() pointed out that it is a BC break. But #21 gives me permission to make that break. :)

benjifisher’s picture

Status: Needs review » Needs work

I am setting the status to NW to address @quietone's comments on the MR.

benjifisher’s picture

Status: Needs work » Needs review

I replied to the MR comments, so back to NR.

quietone’s picture

The small nit was fixed and I think the doc block comment for expandPluginIds() should stay where it is.

This can go back to RTBC, but we are waiting for core tests to pass.

quietone’s picture

Status: Needs review » Reviewed & tested by the community

Let's just set back to RTBC

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

I've created a new MR to show how I think we should handle the deprecation here. Instead of coming up with a new method I think we should deprecate the current behaviour of \Drupal\migrate\Plugin\Migration::getMigrationDependencies() and use a flag to get it to expand them. This way we keep the good name and doing:

$a = $migration->getMigrationDependencies();
$b = $migration->getExpandedDependcies();
var_dump($a === $migration->getMigrationDependencies());

Does not produce surprising results.

benjifisher’s picture

@alexpott:

You make a good point about changing the value of the protected property $migration_dependencies. There are some mitigating factors: the situation you describe in #31 will give unexpected results only if the code uses the deprecated method getMigrationDependencies() AND uses the method set() (to be deprecated in the parent issue) without expanding dependencies. Also, we can avoid modifying the property whether we deprecate getMigrationDependencies() and add getExpandedDependencies() or just deprecate getMigrationDependencies() without setting the new parameter to TRUE.

I am worried about the BC implications of adding a parameter to a public method of the Migration class. Are we covered by the Drupal core backend backwards compatibility and internal API policy?

Particular plugins, whether class based or yaml based, should not be considered part of the public API. References to plugin IDs and settings in default configuration can be relied upon however.

I do not think the Migration class itself counts as a "particular plugin".

The last thing that bothers me about MR 3019 is what the final state will be. That is, in Drupal 10.1 we will deprecate all current calls to getMigrationDependencies(), replacing them with getMigrationDependencies(TRUE). What will we do in Drupal 11? Will we keep the new $expanded parameter and throw an exception if it is not TRUE? Will we change the default value of the parameter? Either way, the final state seems awkward. What if, in some future issue, we decide that we want to add a new parameter to the method?

benjifisher’s picture

Issue summary: View changes
Issue tags: +Needs change record updates

We discussed this issue on Slack during #3322193: [meeting] Migrate Meeting 2022-12-01 1400Z. That issue will soon have a transcript.

I am still nervous about the BC/deprecation strategy, but I defer to @alexpott on this point. See my comment in #32. The plan is to remove the boolean $expand parameter in a future version, so I am happy with the planned final state.


I rebased MR !1797 on the latest 10.1.x and cherry-picked @alexpott's commit from !3019. I then added a few tweaks (in separate commits).

We need to update the change record (CR). I am adding the tag for that.

I am updating the issue summary.

Given that we plan to remove the $expand parameter, I wonder whether we should re-word the deprecation message. I need to sleep on that. Maybe it is fine as it is, and we can do the explaining in the CR.

quietone’s picture

Status: Needs review » Needs work

@benjifisher, sorry for taking so long. I did review the code, and checked the unresolved issues. I didn't find anything that needed another change. Like, you I am uncomfortable with the deprecation so I chatted with catch. He had some ideas but in the end the solution here seems the simplest. I do think the change records should be clear about what is happening to the function argument.

I am setting to NW for the change record update then this can be RTBC again.

benjifisher’s picture

Status: Needs work » Needs review

@quietone:

Thanks for the careful review! I updated the change record to describe the current strategy. I did not suggest any planned future suggestions, such as removing the parameter that we are now adding.

benjifisher’s picture

@rkoller and I discussed this issue at #3327488: Drupal Usability Meeting 2022-12-23. That issue will have a link to a recording of the meeting.

Based on that discussion, I updated the change record (CR). I replaced "Specifically" with "In detail" (in two places).

I will take the last line of Comment #34 as permission to set this issue back to RTBC now that I have updated the CR.

quietone’s picture

Status: Needs review » Reviewed & tested by the community

I read the updated CR and my concerns have been addressed. While I am not familiar with 'in detail' used as a sentence, it was easier to find a definition on-line and it highlights an important piece of information.

I am pleased to set this back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 5637daf and pushed to 10.1.x. Thanks!

It's taken a while but I think this is about as good as we can do wrt to interfaces and encapsulating the logic in the correct place. Making the expandPluginIds method public and moving it the interface is allowed under the 1 to 1 interface to class rule.

Nice work @benjifisher.

  • alexpott committed 5637daf3 on 10.1.x
    Issue #3262395 by benjifisher, alexpott, quietone, heddn: $...
wim leers’s picture

This has confused and bitten me many times over the past 2 years. My solution has been to just always use ::createInstances(), to ensure the expanding of plugin IDs always happens.

Thanks for getting this fixed in core! 👍

benjifisher’s picture

Thanks, @alexpott!

I have un-postponed #2796755: [PP-1] Deprecate Migration::set(). I think that issue is close, now that this one is resolved.

quietone’s picture

In #37 I confirmed that changes to the change record were correct but did not remove the tag. Doing so now.

Status: Fixed » Closed (fixed)

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