Problem/Motivation
Stub entities are created with wrong default values, because all empty entity properties are initialized with null in migrate itself. The attached screenshot illustrates the problem.
Proposed resolution
The base Entity destination plugin should provide default values for unpopulated required fields in processStubRow(). Where that isn't good enough for specific entities, they should override processStubRow() to add their own processing.
Remaining tasks
Tasks:
Test basic stubbing of base fields for all content entity types supporting migration.Test stubbing of custom fields (don't really need separate testing, all fields are handled equally).Implement defaulting of required fields in Entity::processStubRow().Figure out how to stub entity_reference fields.
Entities working with stubbing:
- aggregator_item
- block_content
- comment
- file
- menu_link_content
- node
- shortcut
- taxonomy
- user - includes workaround for #2602066: User name max length differs between storage and field definition.
Entities not currently working with stubbing:
The patch currently contains work-arounds for the following core issues - ideally we would like to get as many of these as possible committed and remove the work-arounds from this patch before committing it (and yet we would also like this patch in ASAP).
- #2536374: Generate placeholder content for ListItemBase Field types
- #2602066: User name max length differs between storage and field definition
- #2602662: Feed ID should be required base field for aggregator items
- #2605150: UrlItem::generateSampleValue() does not generate a valid uri
- #2605254: LinkItem::generateSamplevalue() does not respect link_type setting
User interface changes
N/A
API changes
None
Data model changes
None
| Comment | File | Size | Author |
|---|---|---|---|
| #65 | interdiff.txt | 1.46 KB | mikeryan |
| #65 | create_stub_entities-2590993-65.patch | 40.65 KB | mikeryan |
| #25 | create_stub_entities-2590993-25-FAIL.patch | 24.26 KB | mikeryan |
| #7 | create_stub_entities-2590993-7-FAIL.patch | 12.09 KB | mikeryan |
| #3 | 2590993.png | 771.77 KB | webflo |
Comments
Comment #2
webflo commentedComment #3
webflo commentedComment #4
webflo commentedComment #5
webflo commentedComment #6
mikeryanThis is pretty important - we fixed this for comments, but need a more general fix. Even simple node stubbing fails with NULL titles... Can Entity::processStubRow() automatically identify all required properties/fields and populate them?
Comment #7
mikeryanI started work on #2555127: Process Migration Always Ignores no_stub Due to Pre-Existing Cached Entity Migrations by trying to create a fail test, and realized we basically have no tests of stubbing at all (and also that pretty much any test I constructed would fail due to this current issue). So, I'm starting with tests of stubbing for migrated content entity types, which fail in different ways. I'm not entirely sure whether we can automatically populate all required base field properties, but I'll give it a try. Some entities will certainly still require particular work in their own processStubRow() methods.
Comment #9
mikeryanComment #10
mikeryanI've got the basic solution, which really isn't bad at all. The biggest hole here is stubbing entity_reference fields, since of course we need a valid entity on the other end. Next stage is to see if we can generalize the comment migration approach of looking for an arbitrary entity of the appropriate type and point to it - if not, it may be up to the particular entities to handle it as comment does. And yes, what if there is no existing entity of the right type? No, I do not want to create a second level of stub... This seems like an appropriate place for a SkipRowException.
Comment #12
mikeryanNote that user stubbing fails due to #2602066: User name max length differs between storage and field definition.
Comment #13
svendecabooterTested the patch in #10 with a D7 migration that gives Integrity contstraint violations when migrating taxonomy terms.
The error still persists.
Quick debug showed that the $field_definition->isRequired() seems to return FALSE for the weight field on the taxonomy term.
Will debug in more depth.
Comment #14
svendecabooterHmm seems Drupal\taxonomy\Entity\Term::baseFieldDefinitions() doesn't call ->setRequired(TRUE) for $fields['weight'].
Alternatively, the database structure could have a default value of 0 for this database field, but it does not.
Should we add a workaround for terms in this patch, or try to fix the problem in a related issue?
Comment #15
mikeryanDiscussed with svendecabooter in IRC - I've stepped through the taxonomy term stub test, and in Entity::getEntity()
Produces a term entity with the weight set to 0. Stepping into it I saw that being set in FieldItemList::applyDefaultValue(). He will try debugging his scenario tomorrow to see how that's not being set in his case.
Comment #16
mikeryanIt turns out entity_reference fields do implement generateSampleValue() by doing just what I wanted, randomly selecting a random referenceable entity. So, the question remains, what if there are no such entities (as is the case with the tests right now, which only create the stub in isolation? I do not think we should try to create a second layer of stubs - the stubs we're creating now are proxies for real source system data and (unless they're orphan references in the source) end up getting filled in with real entities, while this second layer of stubs would have no relationship to the source data, and would not be cleaned up by completion of the migration process, they would leave dummy data lying around. So, in this case, I think we should throw a SkipRowException. In most real-world cases, where the migrations are run in some sort of rational order, this should not happen, and the exception should let the migration developer know they need to rethink their dependencies.
The tests that involve required entity_reference fields should be modified to test first with no referenceable entities, verifying that the exception is received, and then to test after creating appropriate entities and confirm that the stubs are properly created.
Comment #17
mikeryanComment #18
mikeryanProgress:
contact and file are the only ones remaining.
Comment #20
mikeryanDuh, we don't migrate contact message entities, so removing that useless test.
Just files left, and there is actually a generate for them, we just need to have the migration plugin skip its attempt to copy a file in the stub case...
Comment #21
svendecabooter@mikeryan: Tested again with my D7 test site, Drupal 8 up to date with HEAD & patch in #18 applied.
Still the problem persists with the migration of a few simple hierarchical taxonomy terms.
The default weight of 0 doesn't get set after
Also, for the weight field FieldItemList::applyDefaultValue() never gets called when I put a breakpoint there.
No idea what the problem is, especially since it's a really simple D7 source.
The vocabulary is used for the Forum module BTW, though that shouldn't be an issue AFAIK.
Comment #22
svendecabooterMore debugging:
\Drupal\Core\Entity\ContentEntityStorageBase::doCreate():
$values["weight"] = NULL, so the if and elseif get skipped.
Comment #23
mikeryan@svendecabooter: OK, I now understand your issue. The Migration process plugin, when a stub is to be created, runs processRow() using the migration's process pipeline. It has the comment "Only keep the process necessary to produce the destination ID", which appears to be a lie - the whole process config is passed to processRow(). For your d7_taxonomy_term migration, this includes 'weight: weight'. Since there is no source data for weight, the destination property 'weight' is assigned NULL (in my tests there was no mapping for weight, so no destination property set for it). So, in the snippet you showed above,
array_key_exists('weight', ['weight' => NULL, ...])returns TRUE.So many possible things we can do here...
|| is_null($values[$name]).Comment #24
mikeryanCreated #2603626: Apply default values when existing value is NULL to see if the doCreate() change will fly. In the meantime, in the context of this patch I'll decide which of the last two options to pursue.
Comment #25
mikeryanOK, here's a fail patch with a test demonstrating the stub-term-parent issue when weight is mapped in the migration, and a patch fixing processRow() to not set NULL destination properties (and a previously-not-uploaded change to remove the pointless contact test). The "good" patch is still going to fail on the file entity stubbing, which is my next task.
Comment #26
mikeryanAdding links to bugs we need to work around in this patch.
Comment #29
mikeryanAnother one - *sigh*.
Comment #30
mikeryanOK, this adds a workaround to #2603726: Uri should be required base field for file entities. That alone is *so* close to working just fine - the one remaining problem after that is that File::preSave() unconditionally calls filesize() on the generated (fake) uri and triggers a "stat failed" warning. The stub entity does, though, get successfully created and validated, the warning is the only issue with that. This patch includes a (admittedly gross) work-around for that, so this should pass the bot and be ready for manual review. Alternative answers to the filesize problem welcome!
Comment #31
mikeryanWell, thinking some more, maybe that solution isn't that gross - a stub file entity should have a stub file, right? And having it point to a real file is a sort of referential integrity...
Comment #33
mikeryanThe first test error is exposing yet another bug outside the direct scope of this issue: #2603798: dblog_settings tests prove nothing.
The second is because the upload test results in stubbing, which in this case is not working with our actual population of the stub, looking into that...
Comment #34
mikeryanOK, two fixes here for the two test errors:
I think this is finally the one that passes...
Comment #35
mikeryanReviewed the patch quickly myself and made one more tweak - EntityFile::processStubRow() started as purely a workaround for #2603726: Uri should be required base field for file entities, but then I added the file creation bit that will be necessary even if/when that one is fixed. Indeed, if that issue got committed, it would break this code by skipping the file creation, so I rearranged it so it will work whether or not #2603726: Uri should be required base field for file entities is fixed.
Comment #36
mikeryanComment #37
phenaproximaIdeally this patch will be blocked on #2602662: Feed ID should be required base field for aggregator items so this extra class will not be necessary, but the other patches may not be RC eligible. Will need to run that past core committers.
Exceptions are not supposed to be translated or formatted -- this should be a totally static string. (Imagine trying to debug an exception in Russian or Thai or Klingon?)
Like with exceptions, I don't think there's any need to translate test messages.
The fail message should not be translated.
So if I understand correctly, we're killing the dependency on RDF here because MigrateCommentStubTest specifically tests the condition which would have caused the RDF fatal?
Ideally, the entity manager (or one of its constituent services) should be injected, but that should be done for all entity destinations and is a huge change deserving of its own follow-up issue. Maybe just add a TODO to inject the service?
I wonder if is_null() is what we want -- what if a generateSampleValue() call returns FALSE or something? empty() might be a better choice here.
Exception should not be translated, and uri does not need to be a variable.
If I'm not mistaken, the file_system service provides a touch() method, so we should probably use that instead.
Again, a TODO about injecting the entity manager would be good here.
Ditto.
Exceptions: Don't translate 'em :)
I wonder if this and createStub() should not be in a trait, since most functional migration tests don't care about how or if stubbing is done.
IIRC, validate() does not return boolean, it returns a list of constraint violations. This should probably assert that violation count is 0.
Ditto here with asserting a truthy ConstraintViolationListInterface.
Comment #38
mikeryanWe'll try to get that one in, but this stub patch really needs in for 8.0.0 (and ideally rc3 if there is one) - we should set a deadline and if that patch isn't in by then, pursue this patch with the work-around.
You didn't explicitly mention it, but we also are ideally blocked on #2602066: User name max length differs between storage and field definition. That one is pretty ugly and I'm not optimistic about fixing it for 8.0.0 unless someone with deeper knowledge of field definition internals takes an interest in it, so I'm inclined on including the work-around in the stub patch.
This and the various other unnecessary translations removed.
Yes, the RDF thing was a horrible indirect bass-ackwards way to "test" the stubbing failure, now we have clean explicit tests.
Done - see #2604566: Inject entity manager and field type manager to Entity destinations.
The default implementation returns NULL (well, the default implementation is
{}) - the interface specifies it should return an array, so changing to empty().Nope, no touching in FileSystem.
Trait created.
Done for this and the other instance. Oops, but there are constraint violations being returned, damn...
Comment #39
mikeryanSo, thanks (semi-sarcastically;) for pointing out the misapplication of validate() - it turns out a bunch of stuff is actually failing validation. I think this is not a fault of the stubbing code itself, but of the tests - the one I've looked at in detail, block_content, is failing because there are no existing block_content_type config entities for its 'type' entity_reference field to be stubbed to point to - so, in this case, we need to test for the failure in the absence of the block_content_type, and success with it. I need to go through entity type by entity type and track this stuff down...
Comment #40
mikeryanThis patch has unveiled yet another core bug (yay?)! #2605150: UrlItem::generateSampleValue() does not generate a valid uri
I'll submit a patch for that issue, and for the time being incorporate a work-around into this patch.
Comment #41
mikeryanComment #42
mikeryanAdded a list of core bugs we're working around to the issue summary.
Comment #43
mikeryanAnd... another core bug discovered and to be worked around: #2605254: LinkItem::generateSamplevalue() does not respect link_type setting
Comment #44
mikeryanComment #45
mikeryanOK, I think this should pass the tests... To recap, phenaproxima pointed out that I was misusing validate() in the tests, and it turns out I was missing validation errors all over the place. Good news - none of them was due to inherent functional flaws in the stubbing support, the approach we're taking in Entity::processStubRow() seems solid. So-so news - some of the validation errors showed tests to be incomplete (mainly needing some more setup for all the pieces to be in place to create valid stubs). Bad news - yet more core bugs found, which we have to work around here until they're fixed.
Comment #47
mikeryanTwo test errors - one was from a test for a related issue I was working on that doesn't belong here (especially without the associated fix!). The other is an error in FileStubTest which I can't reproduce locally, I can't figure out specifically what the issue is but imagine the uri field is the most likely culprit. I've got an improvement to the uri code here, which should make absolutely no difference to the test, but let's run it through anyway...
Comment #48
mikeryanComment #49
mikeryan#2536374: Generate placeholder content for ListItemBase Field types has been committed, so removing the work-around for that one.
Comment #50
mikeryanComment #51
mikeryanSo, we added those todos to inject the entity manager? The thing is, it's already injected - in EntityContentBase. The processStubRow() implementation was added to the Entity class, which doesn't have the entity manager injected - but stubbing is only supported by content entities. So, I've moved processStubRow() to EntityContentBase where it can use the injected entity manager. This just left the issue of injecting the field type manager, and I decided to go ahead and do it as part of this patch.
Related: the entity manager we're using all over the place is now deprecated in favor of a bunch of individual little managers. It's out of scope to replace it in this issue, but I'll open a follow-up. What's the policy on replacing the deprecated call - target for 8.0.0, or should it be done later?
For anyone watching this issue with bated breath - you can help by reviewing #2605150: UrlItem::generateSampleValue() does not generate a valid uri and/or #2605254: LinkItem::generateSamplevalue() does not respect link_type setting, once those get committed we can remove the work-arounds from this patch - I'll then be comfortable trying to get this patch in with only the user work-around.
Comment #53
mikeryanWeird bot glitch? Trying again...
Comment #54
mikeryanEntityManager removal issue: #2607688: Replace deprecated entity manager with specific services. Doing that analysis, it seems like the only current usage of the entity manager is in the processStubRow() implementations, using getFieldDefinitions(), so it just might be possible to replace EntityManager with EntityFieldManager in this patch. Or is that overreaching?
Comment #58
mikeryanFound a test that needed to inject the field type manager.
Comment #59
dixon_I've run into this issue as well, and the solution looks really good to me!
All except one nit-picks below are just the fact that I haven't seen the line break or indentation patterns elsewhere in core. If they do in fact align with core's coding standards, please ignore those points.
Nit-pick: Indentation patterns not common in core.
Nit-pick: Indentation patterns not common in core.
Nit-pick: Indentation patterns not common in core.
Nit-pick: Indentation patterns not common in core.
Should be
// @todoComment #60
dixon_Comment #61
mikeryanI do try to follow https://www.drupal.org/coding-standards#linelength. That being said, I've had trouble getting PhpStorm to not align arguments like
so correcting those.
Done.
Comment #62
dixon_Looks good to me.
Comment #65
mikeryanRemoved the work-arounds for #2605150: UrlItem::generateSampleValue() does not generate a valid uri and #2605254: LinkItem::generateSamplevalue() does not respect link_type setting - once it passes the bot, this is ready to go as far as I'm concerned.
Comment #66
mikeryanOK, a core committer's guide to what's happening in this patch:
Volume wise, the biggest thing added is tests to demonstrate that stubbing works for each entity type supported by migration.
Before this patch, we hacked in specific stubbing support for comments (in particular for the parent entity reference) - we're now able to remove that stuff because the general processStubRow() does it.
File entities are special snowflakes when doing real migrations, but less so for stubs, so we short-circuit the special processing here and a couple places below for stubs.
The one snowflakeness needed for stubs is the need to make sure the stub entity has a real file to point to, so we override processStubRow() for that.
In most cases of real migration, setting a destination property NULL effective behaves the same as not setting it. However, so default values work properly in stubbed entities, we need to leave the destination property unset if there's no real value.
Only content entities support stubbing, so moved the stubbing code to EntityContentBase.
This is the heart of the patch - making sure that every required field on an entity being stubbed is populated with a valid value.
Leaving empty destination properties unset revealed we needed an empty array here, in case all properties are left unset (and require stubbing), else foreach is unhappy.
This class only existed to provide a stub name for terms - no longer necessary with the general stubbing support.
Unfortunately, fixing the root issue here (internal discrepancies in the length of the username field) is a quagmire, so we work around it. Without this, the stubbing code will generate a username too long to fit in the real schema length (60).
Comment #67
dixon_Interdiff looks good. Back to RTBC.
Comment #68
phenaproxima+1,000 RTBC. Migrate needs this, bad. Kudos to @mikeryan for a glorious and stellar patch.
I didn't know assertIdentical() returned a boolean. Seems to me like it should just loop over $violations without asserting its count -- if there are any violations, the test will not pass :) But that's more of a style thing, really.
Nit: There should be a space after (string), but that's fixable on commit.
Comment #69
phenaproximaEverything in here touches Migrate only, which if I'm not mistaken makes it RC eligible. Let's do it.
Comment #72
webchickCommitted and pushed to 8.0.x. Thanks!