Problem/Motivation
Although migrate does have an id mapping system the identifier for node is nid and not vid so vid must be preserved during migration.
This is impossible ATM because when editing an entity the system expects the revision id to be of an existing revision id. Saving the entity at this point can be done two ways: a) a new revision is requested, the (existing) revision id is unset and the database generates a new revision id b) a new revision is not requested and the database record is updated.
But migrate is different: the revision id is NOT of an existing record currently in the database.
Proposed resolution
Allow for this weird edge case.
Remaining tasks
Perhaps extend a test. MigrateDrupal6Test covers this (and half the world. It has 500 asserts, give or take a dozen. Few are the CRUD operations that escape the iron fist of MigrateDrupal6Test.)
User interface changes
None.
API changes
There's a new keepNewRevisionId method which noone should use and it's documented accordingly.
| Comment | File | Size | Author |
|---|---|---|---|
| #42 | interdiff.txt | 1.13 KB | chx |
| #42 | 2211949_42.patch | 11.38 KB | chx |
| #35 | interdiff.txt | 883 bytes | chx |
| #32 | d8_revision_id-32-interdiff.txt | 4.82 KB | berdir |
| #32 | d8_revision_id-32.patch | 10.74 KB | berdir |
Comments
Comment #1
chx commentedComment #2
chx commentedDoxygen fix.
Comment #3
berdirNot exactly sure how, but I think this should better explain what it is and means:
Returns whether a new revision ID should be generated.
When TRUE is returned, the current revision ID must be used to save the new revision. The caller is responsible to set a valid revision ID.
?
I don't think it's necessary to do repeated warnings, what I wrote above is IMHO fine. Just "The new value to set." or so should be enough.
That said, I just had an idea how to solve this without adding a new method.
What if we move the responsiblity of removing the old revision ID to setNewRevision() ? If you call setNewRevision(TRUE), then it will do $this->{$this->getEntityType()->getKey('revision_id')} = NULL.
Then all migrate needs to do is set the revision ID it wants *after* calling that? And we can remove that logic from the storage controller?
Comment #4
chx commentedWould work for me, totally fine. core/modules/node/lib/Drupal/node/NodeFormController.php on line 54 in prepareEntity does a $node->setNewRevision(!empty($this->settings['options']['revision'])); which is pretty much the only case when I can imagine core needing this call and so that's fine. BTW it also does if (!empty($form_state['values']['revision'])) {
$node->setNewRevision();
in submit, is that necessary given prepareEntity? I do not know entity form controllers, just asking. I will write a new patch and test it with migratetermnoderevisiontest just a minute.
Comment #5
berdirIt needs the first call because it uses isNewRevision() for the revision checkbox.
I'm not sure, but I think it's needed when new revision is on by default but then disabled. That's probably broken right now because we only call it when it's said, we should call it with the value of the checkbox. Same for CustomBlockFormController.
That's an interesting point though, this means that the object would already lose the version ID while being edited and persisted like that in the form storage, could that be a problem? I'm not sure...
Comment #6
berdirYes, confirmed that the use case with default revision on but then disabling it manually does not work as expected. I'm always impressed how much stuff we're *not* testing ;)
Not a problem of this issue, but will need a new bug report.
Comment #7
chx commentedComment #8
chx commentedComment #9
berdirI don't think the isset() is necessary, as this is a content entity where the field object should always be there (below it's a stdClass data record). You might want to do a $this->set($revision_key, NULL) instead, though.
Comment #11
chx commentedOK: removed isset, used set.
Comment #13
berdirAh, just revision, not revision_id :) Made that mistake myself a while ago too.
Comment #14
benjy commentedRe-rolled to use revision instead of revision_id as suggested in #13. Also fixed the comment, was a touch long :)
Comment #16
berdirInteresting fails:
- We didn't reset setNewRevision() after saving a revision, so when you save again, it tried to create a new revision again, only this time, it still used the old revision ID. I'm not 100% sure this is correct, but I think it's ok and consistent with isNew(), which will not save multiple new entities either (this is a bit different, though, but why would you want to create multiple new revisions by default?)
- A few calls to setNewRevision() on entity types that don't have revisions. Changed one instance to an entity type that has revisions and removed a bogus setNewRevision() in another.
Comment #18
berdirThis should be better.
However, while looking at that, I noticed that we forcefully call setNewRevision() when saving new entities.. so it is not possible to save a new entity with a given revision_id with the current patch :(
Comment #19
chx commentedHow about this?
Comment #20
berdirWorks for me, did you verify that this works for migrate? I think the code that needs to be updated to use this is now actually in core, so can update it too. Too bad there are no tests for it yet :)
Comment #21
chx commentedYes, everyone's "favorite" (if I had any hair left that test would've turned it white. How helpful I don't.) MigrateTermNodeRevisionTest passes just fine after a very small modification ( (needed to add $node->setNewRevision(); to MigrateTermNodeTestBase::setup when saving the second revision) .
Edit: that this is the right test is clear from the fact that without the modification the test doesn't even finish, the EntityRevision destination fatals out when trying to load a revision that doesn't exist.
Edit2: There's nothing to modify in the migrate that is in core, thanks god. EntityRevision happens to chant the right incantantions in the right order, sets revision newness before running updateEntity.
Comment #22
berdirThen you have a different core than I do I think :)
Both EntityRevision and EntityContentBase call keepNewRevisionId(TRUE) in my core :)
Comment #23
chx commentedOh. Those. Yeah, sorry, they can just be deleted, nothing needs to change to make the MigrateNodeRevisionTest and MigrateNodeTest pass. And yes it's slightly annoying we only have simpletests in the sandbox -- this issue holds the D6->D8 path patch from being submitted and then we will have all the tests in the world.
Comment #24
fagoDiscussed this change in behaviour with berdir, and agreed it make sense. However, this needs to be documented, i.e. the interface documentation should tell us that this @throws this exception if the entity type is not revisionable. Opened the related #2224549: Simplify checking whether an entity type is revisionable.
Else, the patch looks good to me and cleans up things nicely. Thus setting needs work for the missing doc change.
Comment #25
chx commentedComment #26
fagoI think @throws should be above @see and have its description below of it, updated it accordingly.
Comment #27
berdirOk, @fago approved everything else and the doc fix looks good, so RTBC!
Comment #28
fagoYep, thanks - I agree this is RTBC.
Comment #30
chx commented#2134857: PHPUnit test the entity base classes broke this.
Comment #31
berdirYeah, working on it.
Comment #32
berdirThis is one of the days where I don't like PhpUnit ;)
Comment #34
berdirStrange, now this seems to be failing the new Ui test that we added. That could be an actual problem, haven't looked into it yet.
Comment #35
chx commented> It needs the first call because it uses isNewRevision() for the revision checkbox.
And why on earth does it do that instead of checking the options there?? If I remove it from prepareEntity and move it into the form() function it works. Cos calling setNewRevision now loses the vid even if you reset it later.
Comment #36
chx commentedComment #38
berdirTrue!
Yay test coverage! ;)
Just a re-roll.
Comment #40
berdirBetter.
Comment #41
fagoField type manager.
There is no such method on the EM?
Comment #42
chx commentedI removed getFieldDefinition and fixed the comment. Ran phpunit directly against the class to make 100% it doesnt get skipped by the bot ( I hate phpunit for its really finicky way of picking or not picking tests. getInfo is way better. anyways. )
Comment #43
fagoThanks, back to RTBC then.
Comment #44
catchLooks great. Committed/pushed to 8.x, thanks!