We need a migrate destination which stores a compound destination key of entity_id and revision_id .
Keep in mind here that migrating into an ERR *field* is a two step process. First you migrate into paragraphs or whatever that requires ERR. The paragraphs migration is where you use the ERR destination entity. Then you follow that up with a 2nd migration that migrates the relationship into the node or whatever is the content that has the ERR field. Until #2767643: Scalar to array migration returns NULL is committed, you'll need to apply that core patch if you want to get this working. The tests are going to fail here until then.
The patch here let's you run the following scenarios:
Single paragraph:
#source data:
id,photo
1,Photo1 here
2,Photo2 here
id: single_paragraph
source:
plugin: <insert source plugin name here>
ids:
id
type: integer
process:
field_file: <photo>
.
.
.
Other field mappings as needed
destination:
plugin: 'entity_reference_revisions:paragraph'
Multi-valued paragraph destination:
Here there are two migrations that are both migrating in data from different sources. You don't need two, but I did it to demonstrate the possibilities.
#source data 1:
id,author
1,Author 1
2,Author 2
id: multiple_paragraphs_1
source:
plugin: <insert source plugin here>
ids:
author
type: text
process:
field_name: author
.
.
.
Other field mappings as needed
destination:
plugin: 'entity_reference_revisions:paragraph'
#source data 2:
id,author
1,Author 3
2,Author 4
id: multiple_paragraphs_2
source:
plugin: <insert source plugin name here>
ids:
author
type: text
process:
field_name: author
.
.
.
Other field mappings as needed
destination:
plugin: 'entity_reference_revisions:paragraph'
Destination migration
Lastly, this should pull those single and multi-valued paragraph migrations together:
#source data:
id,title,photo,author
1,Article 1,Photo1 here,['Author 1', 'Author 3']
2,Article 2,Photo2 here,['Author 2', 'Author 4']
source:
plugin: <insert source plugin name here>
ids:
id
type: integer
process:
title: title
type:
plugin: default_value
default_value: article
field_paragraph/target_id
-
plugin: migration
migration: single_paragraph
no_stub: true,
source: id
-
plugin: extract
index:
- '0' #very important to use the single quotes otherwise zero is treated as empty and ignored
field_err_single/target_revision_id
-
plugin: migration
migration: single_paragraph
no_stub: true,
source: id
-
plugin: extract
index:
- 1
field_err_multiple
-
plugin: migration
migration:
multiple_paragraphs_1
multiple_paragraphs_2
no_stub: true
source: author
-
plugin: iterator
process:
target_id: '0' #very important to use the single quotes otherwise iterator ignores these.
target_revision_id: '1' #very important to use the single quotes otherwise iterator ignores these.
.
.
.
Other field mappings as needed
destination:
plugin: 'entity:node
Comment | File | Size | Author |
---|---|---|---|
#56 | interdiff_53-56.txt | 842 bytes | heddn |
#56 | entityreference_migrate-2809793-56.patch | 23.74 KB | heddn |
| |||
#31 | interdiff_28-31.txt | 2.45 KB | Charlotte17 |
#31 | entityreference_migrate-2809793-31.patch | 14.25 KB | Charlotte17 |
|
Comments
Comment #2
heddnThere's some core changes that need to happen to make this work. The values, when they are retrieved are numeric indexed. This comes from the SQL lookup. You might open a core issue too
Comment #3
ohthehugemanatee CreditAttribution: ohthehugemanatee at Amazee Labs commentedLinking to the core issue
Comment #4
mglamanHere's a problem - what if we're migrating referenced values that don't have a possible revision (ie: generated on previous migration) Is that what this would handle? Returning the migrated destination ID and revision ID?
Comment #5
heddnRe #4: #2644634-11: Missing Migration Destination in 8.*
The linked core issue above makes life easier, but it isn't strictly necessary. Just cumbersome.
Comment #6
heddnHere's some initial thoughts.
Comment #7
heddnEnough has moved around that the interdiff isn't that useful, so I haven't included one. It was bigger than the actual patch.
Comment #8
heddnThis is the initial stubs of data needed for
getEntity()
. We still need to actually migrate and create an entity to testgetEntity
This should be fixed and @covers added. We are actually testing getEntityTypeId() here.
Comment #9
ruloweb CreditAttribution: ruloweb at Media.Monks commentedIm migrating paragraphs, and the map table doesnt create destid2 column using patch #7, but it does with #6. I think it is because \Drupal\entity_reference_revisions\Plugin\migrate\destination\EntityReferenceRevisions now extends from \Drupal\migrate\Plugin\migrate\destination\EntityRevision, that extends from \Drupal\migrate\Plugin\migrate\destination\EntityContentBase which doesnt calls to parent::getIds() so it returns only revision id.
I created a patch #2820886: EntityRevision should calls parent::getIds(); that tries to solve this, anyway attached is a new version which returns both, revision id and id.
Comment #10
heddn@ruloweb, thanks for your feedback. I hadn't noticed that. This addresses that and add test support for all parts.
Comment #11
heddnDid another quick look at the upstream code in EntityContentBase, and I think we're going to need to override
rollback()
. Primarily because it only make the assumption that we only have a single argument to pass to the storage->load(). And we need to pass both composite keys. But that should be simple, if someone wants to jump on that before I get back to it.Comment #12
heddnI think this might be the last missing pieces. (Famous last words). But let the reviewing begin. Rollbacks now function correctly.
Comment #13
ruloweb CreditAttribution: ruloweb at Media.Monks commentedPatch #12 works pretty ok :) for me, thanks.
Comment #14
ruloweb CreditAttribution: ruloweb at Media.Monks commentedThere is an small tricky bug on last patch, ids order matters, currently it saves [id, revision_id] but in getIds() it returns [revision_id, id].
On this way, destid1 gets revision_id and destid2 gets id, but when migrate tries to get the entity it will think that destid1 = id and destid2 = revision_id. You will see this bug only if you enable stubs #2810907-7: Migrate SQL Map doesn't get array keys for compound keys.
Attached is a patch which solve this.
Im working with an entity/paragraph sctructure like:
and it works pretty ok, thanks so much for all the work :)
Comment #15
ruloweb CreditAttribution: ruloweb at Media.Monks commentedNew patch, It includes the small update from #14 and change how getEntity works, it is inspired a little bit in EntityRevision:getEntity().
In my migration, the nodes get created with ERR stubs, but after I run the ERR migrate, these stubs are not updated, this is because we need to call to updateEntity(), also processStubRow() can be replaced by this function.
Thanks!
Comment #16
TrevorBradley CreditAttribution: TrevorBradley commentedTested and it works beautifully. The migrate map in the database correctly shows both destid's (target_id and target_revision_id) when a destination value of "entity_reference_revisions:paragraph" is set:
(Now if only Migrate::transform would play nicely with this! I'm working on a bug in the Migration process plugin over here: https://www.drupal.org/node/2767643#comment-11768021)
Comment #17
Berdirmissing docblock and empty line after class declaration.
isn't this the same as getDerivativeId() ?
would it be more readable to hardcode the keys 0 and 1, because I think that's what this is doing?
documentation doesn't match the arguments. should also use $entity_id and so on.
revision id is unique, loadRevision($revision_id) is actually enough, which means you don't have to pass the entity_id and actually don't need this method at all?
looking at the test, possibly this is supposed to work if revision_id is missing, but then it would have to set this conditionally?
Comment #18
Ada Hernandez CreditAttribution: Ada Hernandez at MTech, LLC commentedComment #19
Ada Hernandez CreditAttribution: Ada Hernandez at MTech, LLC commentedComment #20
heddnThis isn't necessary. Seems like it got brought in by accident? Or is it necessary?
We probably don't need to check if we have a value in entity_id since all we need is the revision_id. If that is true, we can also remove the entire entity_id variable above then (I think). Check and confirm we aren't using it later on, but if we only needed it for loading, then get rid of the variable.
Comment #21
Ada Hernandez CreditAttribution: Ada Hernandez at MTech, LLC commented@heddn:
that was necessary because this is part of code e.g
Comment #22
Ada Hernandez CreditAttribution: Ada Hernandez at MTech, LLC commentedComment #23
heddnFor some reason, this seems like it was applied against core, not ERR. Re-rolling against contrib.
Comment #24
quietone CreditAttribution: quietone as a volunteer commentedthx, heddn. So, here is first step at a review. I'll need to do more later, especially when I figure out how to run the tests.
Can add a use statement for the TranslatableInterface used in rollbackTranslation.
Where is method loadEntityReferenceRevision?. grep didn't find it.
Let's document the answer to this. Where is the best place?
Has anyone tried the rollback with real data?
I can't get any of these tests to run. What is the command to use?
Comment #25
heddnStrange. Tests are failing locally. They should also on the test bot. Let's see how it fares. Responding to #24 in this patch.
Comment #26
heddnI think I just figured out why. Hopefully we have some failures now.
Comment #28
heddnComment #29
Lowell CreditAttribution: Lowell as a volunteer and at Version3 commentedI successfully ran a migration using this destination plugin
then did a rollback and --force migration both successfully.
I am looking at db tables for content to verify data migrations
Comment #30
quietone CreditAttribution: quietone as a volunteer commented@Lowell, thanks for the testing!
And I found some minor things.
s/migration plugin/migration destination plugin/
Not familiar with @package, is this needed? There are only 4 instances in core and this is the only one if entity reference revisions.
Prefer short array syntax for new code.
This is testing more than get entity. Perhaps the comment can be updated to reflect what this is really doing. And, should rollbackTranslations be tested?
Shows as not used in IDE.
Comment #31
Charlotte17 CreditAttribution: Charlotte17 at MTech, LLC commentedCleaned up the comments to #30
Comment #32
heddnHere's a more complete test. What I haven't figured out yet is how to get the multiple paragraphs migration stuff to work like I'd like.
Comment #33
heddnAnd here's the patch. The migrate process plugin will only ever return the first value passed into it. Seems like an issue for something like this.
Comment #35
heddnWho knew that flags to array_filter were new in php 5.6?
Comment #37
heddnI'm going to need to open some core issues against migrate, because the DX here is fairly painful.
Comment #39
heddnIdeally, I'd be able to structure my source data in this format as well:
However, I cannot do this, because then I have to pass in 'source_ids' and then $value is always ignored and it always returns the first author in the source.
Lastly, it would be super nice if migrate process plugin would allow me to return multiple values. Instead of commenting:
// Break out of the loop as soon as a destination ID is found.
That would make this process even cleaner because I could insert the records in multiple_err_author1 & multiple_err_author2 and use the id column, then pass in id 1, 2 and get back all the entities from both of those migrations that reference id 1 & 2.
While writing up these notes, I figured out a saner way to do the multiple mapping.
Comment #40
heddnComment #42
heddnTests are passing locally. Added some additional assertions to see if we can get more details on what is failing.
Comment #44
heddnI figured out why local passed and test bot didn't. I forgot that core patch #2767643: Scalar to array migration returns NULL is required to making this work. Not marking as NR for that reason. However, if you apply that patch, this patch and its tests pass with flying colors.
I've also added to the IS notes on how to use the patch. And I've got someone in the office here writing up a blog post that spells things out in fuller detail.
Comment #45
heddnComment #46
heddnInterdiff in #44 is fine. Here's the actual patch.
Comment #47
heddn#2767643: Scalar to array migration returns NULL landed so back to NR.
Comment #48
heddnComment #49
heddnComment #50
Charlotte17 CreditAttribution: Charlotte17 at MTech, LLC commentedYou can see the application of this patch #46 set an example in my blog -- https://www.mtech-llc.com/blog/charlotte-leon/migration-csv-data-paragraphs
Comment #51
BerdirWhat exactly is the expected behavior of this method?
This isn't how you delete a revision, this is deleting an entity with all its revisions.
To delete a revision, you need deleteRevision(), but that isn't supported for the default revision.
There are 3 possible scenarios I think:
1. Default revision and single revision
2. default revision but there are other revisions
3. non-default revision
1. and 3 aren't that complicated, we could check for default revision and then either call deleteRevision() or delete(). But no idea what to do about 2. I guess I'm fine with deleting as well then, but we should possibly at least explicitly document this here?
Comment #52
heddnThe expected behavior is to rollback all the data that was inserted. Typically this is paragraphs data. So if someone migrates a few paragraphs from CSV, each of those is probably going to be a new paragraph. And deleting the entity is OK. But if someone is migrating from D7, then on rollback, we only want to delete the paragraph(s) that were migrated, and nothing more. D7 will have revisions, so deleting the full entity might not be what is expected. Based on that analysis, I think that means we want to do 1 & 3, but not #2.
@Berdir, does my description make sense and do you agree with that analysis?
Comment #53
heddnComment #55
BerdirAFAIK the current coding standard is still that variables must be $revision_id, but could be fixed on commit.
Also, test fails look unrelated.
Comment #56
heddnFixed code standards and hopefully the tests now pass. Leaving at RTBC.
Comment #58
heddnComment #59
heddnI'ved opened #2836129: Failing tests on EntityReferenceRevisionsAutocompleteTest
Comment #61
miro_dietikerCommitted, however there is a TODO in the code linking to #2783715: Entity destination ID schema should match entity ID definition
It seems this is outdated. So what's to improve?
Comment #62
heddnWe needed to wait on getDefinitionFromEntity being available. It is now available in both 8.2.x and 8.3.x. How far back do we need to support things? Do we need to wait until 8.1 is no longer supported?
Comment #63
Berdir8.1 is unsupported since the day 8.2 was released :) So yes, it is safe to use it.
Comment #64
heddnI've added #2836432: Use getDefinitionFromEntity inside of getIds migration destination. One thing to consider is that older versions of 8.2.x will not have getDefinitionFromEntity. It was added in 8.2.2.
Comment #65
ksemihin CreditAttribution: ksemihin as a volunteer commentedHi all,
Who have example for use this migration from D7 to D8 site for paragraph entity?
Comment #67
ruloweb CreditAttribution: ruloweb at Media.Monks commentedAs far as I know, this patch is for Drupal 8, and you can see an example in the article posted a few comments above https://www.mtech-llc.com/blog/charlotte-leon/migration-csv-data-paragraphs
Comment #68
quironHi all, thanks for your work on it.
I can not found this syntax/structure documented in the migration docs (https://www.drupal.org/docs/8/api/migrate-api/migrate-api-overview). Any one knows if is documented somewhere?
If not, makes sense to get a page with an example of how to migrate into paragraphs?If yes, I would like to work on it :)
Comment #69
edob CreditAttribution: edob commentedHi all, It's a quick question but does this work with PHP 7 or later??
Comment #70
ohthehugemanatee CreditAttribution: ohthehugemanatee commented@edob - welcome to Drupal! PHP 7+ is recommended for running Drupal 8. It is compatible with PHP > 5.59, but 7 is definitely recommended. So, yes. This works with PHP7. :)
Comment #71
edob CreditAttribution: edob commented@ohthehugemanatee,
Thank you so much, and good to hear that! (btw, sorry for the late reply.)