This is the first stage of #2507607: [META] Replace Cthulhu-forsaken load plugins with migration builders -- defining a builder plugin type whose job it is to generate migration entities from a template, and implementing a few such plugins which do the same thing as what the soon-to-be-deprecated load plugins currently do.
Attached is a first attempt. Review, discuss, and hopefully we can get this in soon.
| Comment | File | Size | Author |
|---|---|---|---|
| #66 | quick-comment-fix.patch | 479 bytes | mikeryan |
| #61 | interdiff-2530030-57-61.txt | 7.45 KB | phenaproxima |
| #61 | 2530030-61.patch | 39.74 KB | phenaproxima |
| #57 | 2530030-57.patch | 39.73 KB | phenaproxima |
| #51 | interdiff.txt | 490 bytes | mikeryan |
Comments
Comment #1
phenaproximaOne thing that will change is MigrateTemplateStorage::load() will go away, since @neclimdul has already provided a patch with a similar method in #2522652: Add getTemplateByName method to TemplateStorage, which is a blocker for this one.
Comment #2
benjy commentedInitial review after a first read, will apply the patch tomorrow and review again.
Should be indented by two spaces :)
Although it would return NULL, it would also through a PHP notice if it didn't exist?
No square brackets need after array here.
Two spaces.
a) I think this method should return the migrations. b) I'm still not sure about the name, mainly because we have another public method called buildMigrations() that does something different.
Two spaces again.
Missing return description.
We're returning $migration->getSourcePlugin(), so maybe this method should be called getSourcePlugin()? Maybe better yet, getEmptySourcePlugin(), oo, but we have an empty source plugin, another name? Is it just a source plugin without an idMap?
Should be NULL, also no quotes?
This is a little confusing how these methods have nothing to do with the builder, but actually something to do with cck field plugin types?
protected
Two spaces.
Missing return comment.
Lets use protected
Previously we were using a colon, eg d6_node:page, any reason to change? Not specifically against it, just asking, seems like an un-needed api change.
missing comment.
We don't use private, should be protected.
Un-needed noise, this patch is probably big enough. Much cleaner though.
lol, please no.
Comment #3
benjy commentedMigrateTemplateStorage::buildMigrations() - How about createMigrations() or createAndSaveMigrations() ?
Comment #4
phenaproxima'null'is a real destination -- Migrate's equivalent of /dev/null. Not sure why it's there to begin with, but it sure is highly useful in this case. A destination plugin must be defined for the migration, or lots of exceptions get thrown.processing_method, perhaps?Comment #5
phenaproximaload() needs to be removed from MigrateTemplateStorage now that #2522652: Add getTemplateByName method to TemplateStorage has landed.
Comment #6
phenaproximaFixed.
Comment #7
mikeryanI haven't done a thorough code review yet, but found these issues when making use of this functionality in #2535228: Make use of proposed builder interface:
Missing [$field_type].
Need to pass $template['source'] as the 2nd arg here, so the d6_node_type plugin can find the source data.
As above, need to add $template['source'] into the 2nd arg.
Need to pass in the 'source' from the original migration and stuff it into $template, to be able to access the source data.
In addition, we have a dependency issue. With this patch, the d6_node template turns into d6_node__blog, d6_node__page, etc. migrations (no migration with ID d6_node is created). Templates like d6_cck_field_values have dependencies on d6_node, which are copied as-is into the generated migrations, which thus fail to run. So, we need a means to substitute dependencies on generated migrations for the original dependency on a "builder" migration. Note that such a substitution is going to be needed generally, even apart from the builder-generated migrations - although for the moment migrate_upgrade is simply using the template IDs as the migration IDs it generates, to support scenarios like migrating from multiple sites each site will need unique IDs for the migrations it generates from the shared templates. E.g., if I'm merging my D6 "blog" and "forum" sites, from the d6_taxonomy_vocabulary template I'll want to make two migrations with IDs like blog_d6_taxonomy_vocabulary and forum_d6_taxonomy_vocabulary (and make all dependencies on vocabularies work for each site).
Comment #8
phenaproximaAll fixed, and the createMigrations() method is now in its own service (migrate.migration_builder).
As we discussed on IRC, migration entities will now store a 'template' key, which indicates which template, if any, the migration was built from. This is to help address the dependency issue @mikeryan mentioned in #7 -- if a migration lists something like
d6_node:*as a dependency, it's shorthand for "every migration generated from the d6_node template" (i.e., every migration withd6_nodeas itstemplatevalue). This logic is implemented in MigrationStorage::loadMultiple().Comment #9
phenaproximaWhoops! Forgot to fix a method call in the d6_term_node builder.
Comment #10
dawehnerJust adapting the issue title for a little bit less confusion.
Comment #13
phenaproximaWhoops! Forgot to inject the entity.query service into Migrate Drupal's overridden MigrationStorage class.
Comment #15
phenaproximaOkay, now I think I understand QueryFactoryInterface. Let's try that again.
Comment #17
phenaproximaProgress! This one oughta work.
It introduces a bit of patch noise by cleaning up MigrateTestCase::getMigration(), which needed modification anyway to fix the failure in #15. I'm unrepentant about it; that method was borderline unreadable.
Comment #18
phenaproximaRemoving a few lines of unneeded junk I accidentally added in the last patch.
Comment #19
mikeryanAs mentioned on IRC, any migration_dependencies on templates whose builders generate multiple migrations from one template (I'm looking at you, d6_node) need to append :* to the referenced template ID.
Still need to play with actual usage of this feature, but a couple of nits on code review:
The... ID?... of the template...
MigrationStorage object
Comment #20
phenaproximaFixed.
Comment #21
phenaproxima/me is an idiot. I forgot to specify the wildcard dependencies in the existing migration templates; fixed now.
Comment #22
mikeryanA couple quick notes as I integrate this with migrate_upgrade:
I don't think the :* should be here, we just want to preserve $template_id as-is.
Needs array_merge() instead of +=.
Beyond that, what I'm dealing with now to make migrate_upgrade run with this patch is the fact that it's exposing missing dependencies in migrate_drupal. For example, d6_vocabulary_field_instance needs a dependency on d6_node:* (without it, it ran before the node migrations - it did not go well). I'm inclined to toss missing dependencies in here, but it could be a separate patch if you prefer. I'll let you know what the missing dependencies are.
Comment #23
mikeryanWhy reorder here?
Should we check source/destination plugin requirements here? Right now migrate_upgrade needs to pass $save=FALSE so it can check requirements before saving the migrations.
Just want to reference here our previous discussion of handling multiple site migrations. E.g., if one is defining migrations from two different D6 sites into one D8 site, then if the template ID is d6_node the dependencies in one site would pick up all node migrations in both sites. However, the front-end tools (migrate_upgrade and its ilk) can probably deal with this by rewriting IDs with per-site prefixes (necessary anyway to disambiguate the migrations themselves), so we're not going to worry about it here at this time.
With the array_merge() change, plus adding d6_node_type to d6_vocabulary_field_instance's dependencies, migrate_upgrade (with the patch at https://www.drupal.org/node/2535228#comment-10148594) works nicely - it looks like everything that was already working continues to work, plus terms are now getting assigned to nodes which was previously broken. Almost ready to rtbc this...
Comment #24
mikeryanRE: the missing d6_vocabulary_field_instance dependency, forgot I had already opened an issue for it: #2535364: Missing dependency in d6_vocabulary_field_instance.
Comment #25
phenaproximaComment #26
phenaproximaWhoops, forgot to make the changes requested in #22. Enjoy!
Comment #27
phenaproximaAdded a basic integration test of the d6_node builder.
Comment #28
mikeryanNeeds a reroll, the d6_term_node* migrations moved to the taxonomy module.
Comment #29
phenaproximaFixed.
Comment #30
mikeryanCode looks good to me, and it works in practice: #2535228: Make use of proposed builder interface.
Comment #31
webchickEscalating priority, based on discussion with phenaproxima and mikeryan. This will enable fixing #2499173: migrate_drupal highjacks the Migration and MigrationStorage classes, as well as pave the way for field migrations from D7.
Comment #32
benjy commentedI'd really like to review this before it gets committed. I'll try look later today.
Comment #33
benjy commentedInitial review, will have to try apply this and test it as well :)
Missing function docs.
should be bool. https://www.drupal.org/coding-standards/docs#types
I'm not sure if this method should be handling the save, seems like it's over-reaching. With other entities in Drupal, they have a "create" method and then you have to call save on the returned entity, there is no shortcut.
Additional space within the array.
Missing docs for the query factory.
"for things". Can we reword that?
Bad things can happen to those who iterate over arrays by reference, especially twice. Lets use array_walk() or just use the key.
We're merging into $group but $group isn't actually used anywhere? Is it trying to grow the variable we're currently looping over? So complicated.
Both @param and @return docs should have two space indent.
Is it arbitrary if we're specifying the plugin_id?
Is there a relevant change here?
OK, so i the concept of the multiple bundle dependencies has moved from the runner to the migration template?
What is the default method? I wonder if we should make it explicit?
Missing constructor docs.
If this is the D6 cck migration plugin, should the class be the same? I see we also have the D6 namespace, these builders won't be re-usable?
If we continue, the next block doesn't need to be elseif().
There is no else block, what does it mean if neither of these are TRUE, maybe a comment?
I think we've standardised on "Gets" in core
O, the method is just hard coded here for d6_node?
Missing docs.
Just tests but still missing docs.
Comment #34
phenaproximaComment #38
phenaproximaSpeedy reroll.
Comment #40
phenaproximaFixed the failing test and moved a couple of builder plugins into their right place.
Comment #41
eclipsegc commentedNitpick, but why'd this move? Seems unnecessary and changes the order of properties being set to be different from the parameters passed.
YUUUUCK. Not your fault, but the fact that the Source plugins require the Migration entity to operate is a bad sign in general.
This is really confusing. These methods don't exist on this plugin so... ????
This seems a pretty big improvement over the existing load plugins for certain. It draws into question a number of the guiding architectural aspect of Migrate for me, but this patch should definitely move ahead and replace what's in core now.
Eclipse
Comment #42
phenaproximaComment #43
phenaproximaRestored MigrateTemplateStorage::__construct(), since it bothers more than one person, and changed the "method" key used by the d6_cck_migration builder to "cck_plugin_method" for clarity.
Comment #44
phenaproximaFixed a sneaky bug @mikeryan discovered in MigrationStorage::expandDependencies(). The method would fail if all the dependencies in a group were variants (template_id:*), and no variants were available.
Comment #45
phenaproximaAdded the d6_node builder to the d6_node_revision template. Shoulda done this a long time ago, but it got lost in the flow...
Comment #46
mikeryanTesting with migrate_upgrade, this fails in the configuration step because it's trying to generate d6_node_revision migrations using the same d6_node__ prefix as the node migrations were already created. I suggest replacing
with
which would also be a step towards making this plugin version-agnostic (need to look at d6_node_type and d6_field_instance references as well).
Comment #47
mikeryanWith that one-line change, migrate_upgrade works with this patch.
I don't see anything specific to D6/CCK here - could this be moved up to the builder namespace and renamed FieldBuilder?
Ditto - is there any reason not to move it up to the builder namespace and rename as FieldMigration? Would it not be reusable for D7 fields?
Can we generalize the d6_node_type and d6_field_instance references so this can be reused for D7 migrations? Or should that be a followup issue?
As previously stated, replacing 'd6_node' with $template['id'] should fix the revision collision issue.
Comment #48
phenaproximaFixed #4. I'd rather hold off on making things generic for D7 until we have the D7 field migrations ready to go, and it's clear what gaps those builders will need to fill in. Chances are they'll be very similar and we will be able to make these D6 builders version-agnostic, but I prefer not to make those assumptions now.
Comment #50
mikeryanTest template needed an 'id'...
Comment #51
mikeryanForgot to upload the interdiff for my tweak.
Comment #52
benjy commentedI think this patch is about ready, i've not tested it in practice but it sounds like Mike has with migrate_upgrade and i've reviewed the code a couple of times. Big +1 to generating the migrations up front rather than LoadEntity magic, that should make debugging much simpler.
One thing I want to note, dependencies are now conceptually more confusing because a template specifies dependencies on another templates "built" migrations for the migrations that this template builds. Previously, a dependency just depended on another migration.
Additionally, templates don't depend on other templates? A template builder doesn't know if the other template has already been processed? Therefore migrations could be generated from one template although the migrations they depend on don't exist yet because that template hasn't been processed? @mike, with migrate_upgrade i'm guessing this isn't a problem because every template is processed and the output is presumably fine, regardless of which order they were generated?
Comment #53
mikeryanSince migration_dependencies are purely a run-time concept - they say "don't run this migration until these other migrations have completed" - there's no requirement to construct migrations in dependency order, they "just" need to all be present when it comes time to run them. So, it is possible to end up with migrations that can't be run because necessary migrations don't exist. I don't think this is generally solvable in the core API, it's the responsibility of the tools built on it to work things out. For example, consider a wizard UI for constructing the set of migrations for a given scenario - the best ordering of the steps from a UX perspective may be different from the required technical ordering of the generated migrations.
There is a significant issue around dealing with migration dependencies in templates, mentioned in #7 above:
The "substitution" referenced was solved for the builder case with the introduction of the :* syntax in dependencies. As for the general case, that's something I'll be trying to work out on the contrib side - if I feel there's something core can do to help support that, I'll open a new issue for it.
Anyway, the whole issue of maintaining dependencies among the migrations created from templates is inherent in the template implementation, it's not an issue created by the builders patch here.
/me thinks dependency management should be added to the "two hard problems in computer science"
Comment #54
benjy commentedOK, I think we're ready here.
Comment #55
phenaproximaDrafted a change record: https://www.drupal.org/node/2544880
Comment #57
phenaproximaQuick re-roll.
Comment #58
phenaproxima...and back to RTBC.
Comment #59
wim leersSorry, but I found several small problems, and lots of missing docs standards violations.
Missing docblock.
Incomplete docs.
This can't be typehinted to
\Some\Class[]?Why not use
[]instead ofarray()in completely new code?Docs.
There are many entity type definitions, so "the" doesn't make sense here.
Violates the rule that this should be a single line, with potentially more detailed explanations after a blank line.
Hrm, a parsed template is simply an
array? That feels weird. Is it not a specific type, or not evenstring[]?We avoid exclamation points and smileys in our docs :)
(I know this because @jhodgdon told me several times :P)
The docs say these are actual instances, but they really are mocked instances. Then this is wrong. Then we write something like:
Ugh. We don't ever do this in core AFAIK.
This is a new pattern, and therefore potentially a bad precedent. I think this is only acceptable if you can point to existing unit tests that do this.
Incomplete docs.
Incomplete docs.
Comment #60
benjy commentedWe've done this before to avoid having to extend the class we're testing when trying to set information onto that class, eg MigrateSqlSourceTestCase uses this approach to set the database and moduleHandler on the class we're testing to mocks. This is all because they're not injected.
However in this case I feel like we should be testing loadMultiple() instead, that's the public method. Testing methods using reflection is usually a code smell. Maybe the argument could be made that testing loadMultiple() wasn't easy/possible and therefore the reflection is needed here as well?
If we do end up needing it, lets at least use \ReflectionMethod which has an invoke() method.
Comment #61
phenaproximaAll fixed. Regarding #11, I created a testing subclass to get at the protected methods rather than using reflection. I like reflection but it's absolutely not worth debating, and certainly not right now.
Comment #62
wim leersMuch clearer :) But this is apparently an array of arrays, so it should actually be
array[]in the docblock? That's a tiny nit though. Back to RTBC!Comment #63
webchickOkay!
@phenaproxima was so kind as to walk me through this patch for an hour or so.
- My first concern was that this sounded like a fairly fundamental change to the underlying Migrate API. However, it's really isolated to migrations with dependencies on dynamic information, which in core is basically just nodes/fields and vocabs/terms. So this patch should not have widespread consequences for existing sites/migrations. It also makes the Migrate API much more grokkable.
- The change record is more expansive than change records usually are, but OTOH you kind of need all that background in order to understand what you need to change and why. Just a note though that in general, where such background is necessary, it's best to put it under https://www.drupal.org/developing/api since those docs will stand the test of time. (Versus change records are only for people upgrading from 7->8 which will more or less dry up after ~2 years.)
- Had some questions about this:
This looks a lot like we're re-creating the config dependencies system. Adam explained that this is not the case, because the config dependencies system only happens at install time, and this is something that happens during runtime.
We also talked a bit about this gem. :P
Ugh. Adam explained this is caused the close coupling between source plugins and migrations. There's an issue to clean this up at #2543536: [meta] Reduce/remove tight coupling of migration classes though it's quite invasive and may be D9 material at this point.
So those things aside, I couldn't find anything to complain about. Sounds like this results in a much more predictable Migration API for everyone, and has +1s from benjy, EclipseGc, mikeryan, etc.
Therefore!
Committed and pushed to 8.0.x. WOOHOO! :)
Comment #65
mikeryanOops, the d6_node:* dependency in d6_comment.yml got lost in the final patch, quick little patch to fix it on its way once I complete a manual test.
Comment #66
mikeryanThis works with migrate_upgrade.
Comment #67
phenaproximaLooks good.
Comment #68
benjy commentedCan we not test for this stuff? Seems the d6_comment template is broken without it?
Comment #69
alexpott#66 should be a separate issue and have tests.
Comment #70
webflo commented@mikeryan I have trouble with config dependencies in #2550309: Unable to import migration config-entities generated by migrate_upgrade module. Maybe thats related to your patch in #66?
Comment #72
quietone commentedI noticed the change record was not published, so I published it.