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_dependenciesto 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.
- When the variable is set, ensure that it is an array of arrays, with the expected outer keys. Previously, this check was in
getMigrationDependencies(). - Add the optional, boolean parameter
$expandtogetMigrationDependencies(). The default value isFALSE, corresponding to the current behavior. - Deprecate
getMigrationDependencies()without setting the new parameter toTRUE. - When
$expandis set, "expand" the lists. Previously, the lists were expanded when$migration_dependenciesis set bycreateInstances(), but not inset(). - The method
expandPluginIds()in the plugin manager is nowpublicso 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
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
Comment #2
benjifisherPersonally, 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:
So I think the safest thing to do is
getMigrationDependencies()and do not change its return value.getExpandedDependencies(), that appliesexpandPluginIds()to the return value but does not change the class variable.getMigrationDependencies(), go to a strict expand-on-access strategy.For (2), other possible names are
getDerivedDependencies()orgetAllDependenciesIncludingDerivatives(). (shrug)For (3), I mean we can update
createInstances()so that it does not applyexpandPluginIds().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.Comment #3
benjifisherHow 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). ThenexpandPluginIds()can be implemented asaddDerivatives(..., $this)andgetExpandedDependencies()can calladdDerivatives(..., $this->migrationPluginManager).A variant of that idea is for the trait to define
expandPluginIds(array $migration_ids, ?MigrationPluginManagerInterface $manager). If$manageris 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()usesexpandPluginIds()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 ofexpandPluginIds().Comment #5
benjifisherOuch, 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.
Comment #6
benjifisherI 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 useaddDerivatives()as in #3.Comment #7
benjifisherYesterday'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.
Comment #8
benjifisherI added a draft change record, so I am removing the issue tag.
Comment #9
quietone commentedI 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.
Comment #10
benjifisherI 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 removeset()andgetMigrationDependencies(), we will have the constructor and the twoadd*methods that change$migration_dependenciesand 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?
Comment #11
quietone commentedOne thing I don't see is a test with a deriver and following from that we can't have a fail patch.
Comment #12
quietone commentedSetting to NW for the unresolved issue and the question in #11.
Comment #14
benjifisherI 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.
Comment #15
quietone commentedHmm, 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.
Comment #16
benjifisherI 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.
Comment #18
benjifisherI 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.
Comment #19
benjifisherComment #20
quietone commentedI 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!
Comment #21
alexpottI'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
Comment #22
benjifisher@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.
Comment #23
alexpottWe 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.
Comment #24
benjifisherI replied to the comments on the MR and updated it as follows:
expandPluginIds()public (#21).MigrationPluginManager::getDefinitions()outside the loop.The second change simplifies the
array_map()inMigration::getExpandedDependencies(). It also simplifiesMigrationTest, since only one method from the plugin manager has to be mocked, not two.Back to NR.
Comment #25
benjifisherI 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. :)Comment #26
benjifisherI am setting the status to NW to address @quietone's comments on the MR.
Comment #27
benjifisherI replied to the MR comments, so back to NR.
Comment #28
quietone commentedThe 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.
Comment #29
quietone commentedLet's just set back to RTBC
Comment #31
alexpottI'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:
Does not produce surprising results.
Comment #32
benjifisher@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 methodgetMigrationDependencies()AND uses the methodset()(to be deprecated in the parent issue) without expanding dependencies. Also, we can avoid modifying the property whether we deprecategetMigrationDependencies()and addgetExpandedDependencies()or just deprecategetMigrationDependencies()without setting the new parameter toTRUE.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?
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 withgetMigrationDependencies(TRUE). What will we do in Drupal 11? Will we keep the new$expandedparameter and throw an exception if it is notTRUE? 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?Comment #33
benjifisherWe 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
$expandparameter 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
$expandparameter, 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.Comment #34
quietone commented@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.
Comment #35
benjifisher@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.
Comment #36
benjifisher@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.
Comment #37
quietone commentedI 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.
Comment #39
alexpottCommitted 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.
Comment #41
wim leersThis 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! 👍
Comment #42
benjifisherThanks, @alexpott!
I have un-postponed #2796755: [PP-1] Deprecate Migration::set(). I think that issue is close, now that this one is resolved.
Comment #43
quietone commentedIn #37 I confirmed that changes to the change record were correct but did not remove the tag. Doing so now.