Problem/Motivation
There are a 4-5 process plugins in core which use the following pattern:
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrateProcessInterface $migration_plugin) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migration = $migration;
$this->migrationPlugin = $migration_plugin;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$migration_plugin_configuration = $configuration + [
'migration' => 'd6_filter_format',
];
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_plugin_configuration, $migration)
);
}
These process plugins need to look up a previous migration result, and do so by creating a copy of the (deprecated) migration plugin.
Additionally, the migration_lookup plugin is problematic. It has a number of inconsistencies in its output, uses deprecated methods,
Proposed resolution
Let's turn the result lookup and stub creation into injectable services. The services can be directly injected into the process plugins that require them, and the migration lookup plugin can be a wrapper around these services.
Remaining tasks
Define interfaces
Define services
Refactor migration lookup plugin to use these services
Write tests.
Refactor other plugins that use migration_lookup to use the service instead. (follow ups)
User interface changes
None
API changes
Provide new migration lookup service and interface
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #59 | 3004927-59.patch | 1.85 KB | quietone |
| #51 | interdiff.3004927.45-51.txt | 20.77 KB | mikelutz |
| #51 | 3004927-51.drupal.Create-Migration-Lookup-service.patch | 69.8 KB | mikelutz |
Comments
Comment #2
heddnTriaging issue queue.
Comment #4
mikelutzInitial crack at Migration lookup and stubbing services. Decided to make them separate services for clarity
Comment #5
mikelutzComment #6
mikelutzAdding updates for the first process plugin for testing.
Comment #7
heddnThe constructors of plugins are explicitly not part of the BC contract. What is the reason we are doing this complicated BC layer here? Do we have a lot of folks that are extending file field?
I'm pretty sure this requires $source_ids to be an array based on the type hint.
Can we type hint an array here?
Comment #8
mikelutzRather broken WIP, posting just so I don't lose progress. Please ignore.
Comment #9
mikelutzRerolling, fixing a few tests, planning to start moving on this again.
Comment #10
mikelutzA few more test fixes, trying to get this PoC mostly passing before I work on refactoring and tests.
Comment #11
mikelutzMore test fixes before cleanup.
Comment #13
mikelutzoops...
Comment #14
mikelutzComment #16
mikelutzHopefully this is the last iteration before cleanup.
Comment #17
andypostI suggest
stubMigration()as method nameSomething wrong with naming here
- it creates stub just internally to return destination IDs
- also under the hood it importing row and that's what it returns
Comment #18
mikelutzBeginning cleanup here.
I might change it to createStub(). A stub migration is a completely different thing than a stubbed destination import, which is what we are doing here.
That's what a stub is though. Exactly what we are looking to have happen at the moment. There is a slight discrepency in the documented return value of MigrateDestinationInterface::import() and what is expected by idMap, but that is how the whole system currently functions. I hope to clean it up somewhat here but I'm a bit limited for BC reasons.
Comment #19
heddn+1 on general approach. Some minor feedback below.
This doesn't stub any more.
Nit, we've been typically using double quotes, '"', around variables in error strings like this.
I agree, we need something like "create" as part of this method name.
Comment #20
mikelutzHere's an iteration addressing @heddn's feedback specifically. On to more tests. :-)
Comment #22
mikelutzFix existing test and coding standards. bumping to major as this blocks D9 deprecation work around the old Migration plugin.
Comment #23
mikelutzHere's a test suite for the lookup service, I just need a test suite for the stub service.
Comment #24
mikelutzComment #25
mikelutzIf I had a nickel...
Comment #26
mikelutzHopefully the rest of the tests I need. Need to run it through testbot while I review for any more cleanup I want to do.
Comment #28
mikelutzAlright, I think this is ready for review now. I'll address in advance my least favorite part of this patch, which is the CreateStubRaw() method on the stub service. I absolutely hate it, but have spent a ton of time trying to come up with a better solution and I'm convinced that this is it.
#2890690: MigrateLookup plugin has inconsistent return values. touches on the problem, but the issue is that the return value from MigrateDestinationInterface::import() is very poorly defined,
@return mixed The entity ID or an indication of success.and yet the entire id mapping and lookup systems are based on the assumption that ::import is returning an array of destination identifiers. Thankfully all the destination plugins do return this array, but they are inconsistently keyed. The core destination plugins return a numerically indexed array of destination identifiers, but some rather important contrib destinations (I'm looking at you, entity_reference_revisions....) return an array of identifiers indexed by the destination keys. The id mapping system compensates for this, but migration_lookup returns the value of ::import directly if a stub is created, which may not be consistent with what the id map returns if a stub is not created.Long term I would like to fix this inconsistency in the destination plugins themselves (and starting by updating the documented return value for ::import(). Short and medium term, It really makes the most sense for these services to consistently return associative arrays. Unfortunatly, the current migration_lookup still needs to return the raw value from ::import, which is why I've added the internal createStubRaw() method. The intention is to have a followup to create a new migrate_lookup plugin that fixes a number of problems with migration_lookup that can't be fixed easily in a BC manner and deprecate migration_lookup and createStubRaw() at the same time.
The other option I considered was to simply not refactor migration_lookup to use the new stub service, and just have it stub on its own as it always has. I could go this route if it's deemed best, but I like the additional implicit test coverage that comes from using the service.
Also of note, the createStub method contains a default_values parameter that isn't currently used in migration_lookup. It's long been a request in the community to have the ability to have less gibberish values for things like node titles when stubs are created. I expect to use the $default_values parameter in a new migrate_lookup plugin to allow people to do this, and it is also available for people to use in custom process plugins so that they can have this ability.
Finally, I introduced a migrate logger channel in this patch, one that I hope to inject throughout the migration system. Another big complaint about the migration system is that there is very little information available when things go wrong. This patch is the first to add logging to the migration system, which should help people track down issues with migration lookups and stubbing.
Comment #29
heddnI like these as 2 different services.
If we plan to mark this as deprecated, let's mark it as @internal. Would that help in any way?
A description and example of what we are doing here would help.
Comment #30
mikelutzOh, it's internal. :-)
Comment #31
mikelutzComment #32
heddnI like it. Let's just bounce over to RTBC and see how that fares.
Comment #34
mikelutzComment #35
mikelutzUpdated IS and CR
Comment #36
larowlanThis looks great, nice cleanup and solid test-coverage.
note to self: we're using a scalar type-hint here so this is solid 8.8 only territory.
but we don't use a return type-hint...but we have no cases of that in core (yet) so we shouldn't start doing that here in my opinion
These are fairly silent errors. Should we return a value object instead that has a ::lookupSucceeded method and a ::getResults so calling code can work out if/what went wrong? Or throw exceptions?
Same here
It seems that the only difference between createStubRaw and createStub is this hunk - should we add a fourth parameter to createStub something like
bool $keyByDestinationIdsor similar and then just have the one method? Or does that paint us into a corner if/when we remove the issues in the various plugins where the return is not consistent?Again, these are very silent failures. It is very reminiscent of a lot of the stuff we used to have in file.inc that now (thankfully) throws actual exceptions so you don't need to go trawling through logs to work out why your image styles aren't getting created :)
Is there are reason why we don't throw exceptions for these things that are clearly wrong. e.g. the 'you must specify the id of a specific derivative' is basically a bad method call or logic exception - shouldn't we convey that? Its not really something an admin/site owner (who logs are generally for) can fix, its something the calling code (developer) should fix - so an exception might be more appropriate?
If this is internal, we shouldn't put it on the interface - as that conveys a mixed message - and once its there, its forever.
can't we just do a hard cast here?
And avoid the if?
Comment #37
mikelutz#1: okay
#2,3,5: I have not addressed these in this patch, but I am working on them. The reason that I have tried very hard to not throw exceptions is that this code runs during a data migration, so If we throw a random exception that the migration executable is not configured to catch and gracefully shutdown from then we can leave the system in an undesirable state. That having been said, perhaps the service is not the place to catch and handle the exceptions, perhaps we need to catch them in the process plugin and either handle them there or rethrow them as MigrateExceptions that the migration system can then catch and shut down the migration safely.
#4: At points during development, I've coded it up both ways, and ultimately picked going with a second method because I thought it was cleaner. Here's a version that implements it as an optional 4th parameter that isn't on the interface. I think it works fine this way too. If the service is replaced, the extra parameter is ignored, and by setting the default to NULL instead of TRUE, we can choose to deprecate that parameter later, if and when we are ready.
#6: we've removed the method, so this is no longer an issue.
#7: I only changed the variable name there, not the logic, but yes, we can cast there. I was originally worried about casting if somehow we got a stringable object there, but the value comes from a migration yml file, so it can only be a string or an array.
Comment #38
mikelutzHere's a refactor of the migrate.lookup service to throw exceptions, and then make sure they are caught and rethrown as a MigrateException in the process plugin.
Comment #40
mikelutzRefactor on the stub service. This should address all issues, but might still have a failing test and/or coding standard fix that I missed.
Comment #41
mikelutzSo, throwing an exception on a derived migration is a bc break, those should just not be stubbed in the current process plugin. When we build a new one, we will require derived migrations to have a no_stub:true configuration..
Comment #43
mikelutzI also realized that I'm going to need to have the ability to pass an array of ids into the lookup service, so I've updated the typehints and documentation on this.
Comment #44
heddnNot seeing much to comment upon.
This seems out of scope.
Let's be consistent w/ our type and return type hinting.
Do we need to trigger error (yet) on this as deprecated for keying by destination id?
Comment #45
mikelutzComment #46
quietone commentedSome feedback on coding standards and comments.
Values should be surrounded by single quotes, not double quotes.
My first though was 'Is there an indirect lookup?'. Can we explain what direct means here.
Can we have something clearer than 'wrong length' as it sounds like a problem with string length whereas it is about finding extra ids.
No longer used.
Seem to have an extra argument, '0'. Can be removed.
s/../.
Comment #47
quietone commentedWith this removed the createStubRow method is no longer used. Remove it?
createInstances expects the parameter to be an array but a string is being passed.
Why pass the logger?
Why pass the logger?
Regarding this parameter, In the comment in #28 paragraph 2 mikelutz states
I feel like this info should be in the comment, that is, that core returns one type of array and that contrib may be different.
Comment #48
quietone commentedAnd finally,
Quotes are not needed. They all can be removed. Of course, this applies to the other test module as well.
Comment #49
quietone commentedAs soon I pressed 'enter' I remembered this one as well. The test coverage in all the tests looks good to me. However, I'd like to see MigrateLookupTest also test with an id that is a string. A common example is a language code.
Comment #50
quietone commentedCame back to look at something and noticed a typo.
s/inheritDoc/inheritdoc/
Comment #51
mikelutzI agreed with and addressed all feedback. Specifically:
46.1 - I replaced everything with single quotes.
46.2 - eh, originally that was the internal method that performed a direct lookup against a single migration versus what was going to be a couple wrappers/helpers that used that method to perform lookups by tag, or on multiple migrations or whatever. Regardless, I clarified.
46.3 went with 'wrong number of keys'
46.4 fixed
46.5 fixed to the original intention, also updated a previous chain exception to use the original code rather than a hard-coded zero
46.6 fixed
47.1 I deprecated it, though I want to deprecate the entire plugin at some point..
47.2 I changed to an array, though for the record, createInstances says
The method logic works for both strings and arrays, and the signature doesn't typehint array, so that should probably be
@param string|string[]47.3 The logger was a leftover from a previous iteration on this service. The tests still passed so I didn't notice. fixed.
47.4 The logger was a leftover from a previous iteration on this service. The tests still passed so I didn't notice. fixed.
47.5 Updated the param documentation
48 I removed the quotes as we don't seem to have them in other test migrations, although most everywhere else in core, values in a yml file with spaces seem to be surrounded by single quotes (not double like I had) should we update them all?
49 I added a test migration with string keys.
50 fixed.
I also removed some php 7 only code that we are not supposed to be using until 8.7.0 is EOL
Comment #52
quietone commented@mikelutz, thanks for the fixes.
There are two issue about quotes in yml files, though specfic to info.yml.
#2116897: Adopt identical YAML formatting for info.yml files
#2994928: Quote consistency in .info.yml files for Core modules
Why don't we make an issue to fix the doc on createInstances? I can do that later today if noone else gets to it.
Comment #53
quietone commentedAnd this can go back to RTBC. It was back in #32 and later larowlan reviewed it noting that it " looks great, nice cleanup and solid test-coverage". Since then it has been cleanup up some more and another test added.
Comment #54
larowlanComment #55
larowlanJust a couple of minor things, but otherwise, this is looking good
ubernit: array_merge with an empty array is a nil-op, so we probably don't need the if here i.e. we could write this as
These don't meet PHPCS, we should be detailing the scenarios in which this can happen or similar.
Comment #56
larowlan@mikelutz pointed me at https://www.drupal.org/node/1354#throws
Comment #58
larowlanFixed on commit
Committed 63ecb2d and pushed to 8.8.x. Thanks!
Published change record
Comment #59
quietone commentedI'm having trouble with a test in commerce migrate and have tracked it to this issue. I create a new test, MigrateUrlAliasNoTranslationTest.php that does not run d7_node_translation. This test fails on HEAD but passes when the commit in this issue is reverted. This patch has just that new test.
I have learned that when a migration does not exist, in this case d6_node_translation, that migrate lookup with throw a pluginnotfound exception which is caught when processing the pipeline and then import is never run. Before this patch, when a migration was not found the pipeline would store an empty value in the destination property ($row->setEmptyDestinationProperty($destination)) and the pipeline would continue to be processed.
Comment #60
mikelutzI opened #3086952: Migration Lookup should catch PluginNotFoundException exceptions and ignore them. as a follow-up to address this
Comment #61
quietone commentedOops the test class is incorrectly named. Just a reflection of how well my day is going. I am really looking forward to the long walk into town soon for dinner and the symphony.
But I see that mikelutz has fixed that in the followup. Thanks.
Comment #62
mikelutzYes, I caught that and did a little refactoring. The test itself was valid, and I saw what you were trying to do.
Comment #64
chicodasilva commentedWhere can I find an example of the usage of the Lookup service?
Francisco