Problem/Motivation
Drupal 8 link fields store route name and parameters and options. Drupal 6 link fields store paths. Drupal 8 link fields, at this moment, does not have a working conversion between paths and route name/parameters.
Proposed resolution
There are proposed solutions in core #2235457: Use link field for shortcut entity but this issue circumvents the complexities of that issue to allow users to begin testing migrations.
The menu link migration added the Route process plugin and an example of using it. We need to use this same circuitous way because the link field can store that. The challenge is to coax the entity load plugin to use the new CckLink process plugin which is based off the current Route process plugin.
Remaining tasks
User interface changes
API changes
Original report by @ultimike
Based on my not-so-good results of larger and more complex D6 databases, I figured that I'd start with something really simple - a fresh D6 site with limited content types and fields.
It seems like there is an issue with link fields, as while the data appears to be migrated (I can see it in the D8 database), there is still something odd going on as the view and edit pages for the nodes return WSOD. Could be related to https://drupal.org/node/2233901, as multiple migrations didn't run due to requirement issues.
I'm planning on re-running this test once https://drupal.org/node/2233901 is resolved, or after I add some new revisions to my D6 nodes.
Here's what I did:
- Enabled the "Content" and "Link" modules in D6.
- Added a new link field to the "story" content type in D6, added content.
- Enabled the "Link" module in D8 (disabled by default).
- Ran the migration (using https://drupal.org/comment/8615887#comment-8615887): nodes migrated, link field created, link content not migrated, with errors:
~/Sites/imp $ drush migrate-manifest mysql://imp@localhost/drupal6 manifest.yml</li> <li>Migration d6_cck_field_revision:page did not meet the requirements [error]</li> <li>Migration d6_cck_field_revision:story did not meet the requirements [error]</li> <li>Migration d6_cck_field_values:page did not meet the requirements [error]</li> <li>Migration d6_cck_field_values:story did not meet the requirements - Based on migration_dependencies listed in the the .yml files for d6_cck_field_revision and d6_cck_field_values, I added the following migrations to the manifest:
d6_node_revision d6_field_formatter_settings - Ran the migration: nodes migrated, link field created, link content migrated (I can see it in the DB), but with errors:
~/Sites/imp $ drush migrate-manifest mysql://imp@localhost/drupal6 manifest.yml Migration d6_cck_field_revision:page did not meet the requirements [error]</li> Migration d6_cck_field_revision:story did not meet the requirements - Then, when I go to the D8 site in a browser, both the "view" and "edit" pages for the newly migrated nodes have fatal errors (http://note.io/1lwAAUH and http://note.io/1dZePNj).
-mike
| Comment | File | Size | Author |
|---|---|---|---|
| #65 | interdiff.txt | 605 bytes | benjy |
| #65 | 2233883-65.patch | 14.52 KB | benjy |
| #63 | interdiff.txt | 547 bytes | benjy |
| #63 | 2233883-63.patch | 14.46 KB | benjy |
| #62 | interdiff-58-62.txt | 996 bytes | hussainweb |
Comments
Comment #1
ultimikeComment #2
ultimikeAttaching source (D6) database. UID 1 user/pass: admin/admin
-mike
Comment #3
ultimikeI just pulled a fresh copy of the imp sandbox and retried the same test - the situation has taken one step forward and one step back.
First, the forward step - the "did not meet the requirements" errors no longer appear when running the manifest, so that's good.
But, after I run the manifest, the /node and migrated node pages are WSOD. Screenshot of the error dump (they're all similar): http://note.io/1lOrfdy
Debugging the issue, I found that in the D8 (destination) database, in the node__field_url (this is my field of type "Link") table, the field_url_options was null for imported links. When I created a link through the UI on D8, the field_url_options was populated with a serialized array:
a:3:{s:5:"query";a:0:{}s:8:"fragment";s:0:"";s:10:"attributes";a:0:{}}Manually populating the migrated node__field_url record with this data and rebuilding the cache allows the node to be rendered in D8. This proves (at least to me) that this is the issue.
It appears to me that perhaps the D6 link migration should be initializing this value as such? I'm happy to work on a patch for this, but where do I start? Is this type of thing handled in the Destination class? Maybe migrate/lib/Drupal/migrate/Plugin/migrate/destination/???
-mike
Comment #4
ultimikePulled latest code, re-ran test, same results.
Digging in a little deeper into this issue, it looks like that when a link is created in D8, the /Drupal/Link/Plugin/Field/FieldWidget/LinkWidget massageFormValues() function creates the options and adds them to the url structure.
When we're migrating link fields in, the massageFormValues() function never runs (obviously), so the options never get set, and therefore the field is set to null, leading to the WSOD.
If all this is correct (big if), then I would think that we need a mechanism to look at the incoming link data and create options, if necessary. Unfortunately, I'm not sure the best way to do this. It seems like this would be more of a Migrate thing rather than a Migrate Drupal thing? I'm happy to do the leg work on this one, I just need some direction...
Thanks,
-mike
Comment #5
chx commentedCurrently
But it probably needs to look like:
And so change LoadEntity to get the process plugin manager injected ( implement ContainerFactoryPluginInterface ) and then use it to check for the existence of d6_cck_$field_type and if so then use it. Also, write the plugin for link field.
There seems to be a core bug lurking here as well: if $node->some_link_field->options is not set then $node->save() fails.
Comment #6
ultimikeFollowing up on chx's previous comment,
-mike
Comment #7
ultimikechx,
In IRC you mentioned:
Should I be creating the node type and link field manually then writing some PHP that calls $node->save()? Your whole "3 line script" is throwing me off - give me a hint?
Thanks,
-mike
Comment #8
chx commentedPlease see NodeLastChangedTest for a DrupalUnitTestc creating nodes.
Comment #9
ultimikeI'm working on modifying the \Drupal\link\Tests\LinkItemTest to test specifically for the case when a link doesn't have any query/attribute/fragment options. In my initial go at it, the test still passed - the entity was loaded.
chx gave me some guidance and I modified the initial test as such:
At this point, I'm thinking that the WSOD I saw (see comment 3 above) happens after the entity is loaded (maybe during rendering?), but I want to debug and inspect the $entity variable in the above code before I do anything else.
Unfortunately, I've never tried to debug a Simpletest before. Debugging from the browser doesn't seem doable (I think Batch API gets in the way?) and I'm still trying to figure out how to set up phpStorm to debug core/scripts/run-tests.sh
I found this article about setting up a Run/Debug configuration in phpStorm, but I haven't been able to get it to work yet...
-mike
Comment #10
ultimikeOkey dokey, some progress...
I figured out what the issue was with being able to debug Simpletests using PhpStorm and xdebug. Turns out the version of xdebug (2.2.0) that came installed with my stack (MAMP Pro 2.1.1) was buggy, after I updated xdebug to 2.2.3, all was well.
Based on chx's suggestion, I created a new Link module test: LinkFieldItemNoOptions - it is virtually identical to the LinkFieldItem test, but with no link options (fragment, attributes, query) set or tested for. Running the test results in no fails (bummer?!) I think I need to figure out how to write a test that renders an entity with no options to see if I can get it to fail (like my comment 3 above).
As chx mentions above (comment 5), we may have to add new process plugins for fieldtypes to handle these types of situations.
Thanks,
-mike
Comment #11
ultimikeComment #12
chx commentedThinking more of it: there is already a per field type mechanism already -- it's the entity_field plugins. They aren't used yet but I think the simplest would be to use those. See ContentEntityBase::import .
Comment #13
ultimikeI just tested a core patch that appears to solve this issue: https://drupal.org/comment/8703857#comment-8703857
Here's what I did:
1. Fresh D8 site.
2. Applied patch: https://drupal.org/comment/8698261#comment-8698261
3. Ran the migration (as outlined in the issue summary).
4. Received no "did not meet the requirements" errors.
5. Content imported cleanly.
6. Was able to view rendered links on node pages with no issues.
At this point, I suppose that this issue can be closed as soon as https://drupal.org/node/2235457 is committed.
-mike
Comment #14
chx commentedI commented on the issue that seems to solve this which is #2235457: Use link field for shortcut entity. Thanks!
Comment #15
ultimikeComment #16
chx commentedComment #17
Anonymous (not verified) commentedThis remains broken. The two possible patches cannot be rerolled to PSR-4. There is currently no pathway to get this working using the existing patches.
Comment #18
chx commentedComment #19
chx commentedComment #20
benjy commentedLets try get this started.
Comment #22
benjy commentedForgot to add the new file.
Comment #23
benjy commentedBetter, yet. Use FieldStorageConfig
Comment #26
chx commentedI guess the fail is the field storage / field config entity itself not migrating.
That said, I am OK with the approach. It's migrate_drupal not migrate so we can restrict the super generic to something to be usable. That's why we moved over the load entity plugin too.
Comment #27
benjy commentedThe problem is that the loading of the migrations happens before any of the migrations actually run. Current patch fixes that issue but I think there still could be a problem in the dumps related to nodes and node revisions. See what the bot says.
Comment #29
benjy commentedShould fix the individual tests, let see what MigrateDrupal6Test says.
Comment #31
benjy commentedReverted the dump code I uncommented by accident in #29.
Comment #32
chx commentedAnd there was much rejoicing.
Comment #33
benjy commentedComment #34
benjy commentedComment #35
alexpottMissing @file
Not used.
@todo what? this should describe something to do and not just vent.
Unrelated change
Shouldn't we test migrating a field with link attributes?
Comment #36
benjy commentedThanks for the review, all feedback addressed. Plus fixed a bug with link options/attributes and added support for "external".
Sorry about the TODO, I was genuinely meant to go see why D6 does that. I'm still not entirely sure why but I did confirm, they're stored in the database in D6 double serialized.
Comment #37
chx commentedRound #2.
Comment #38
benjy commentedI tested this tonight with a real migration and it still doesn't seem to work. Not sure what is going on at this point. Setting back to NW until I figure it out.
Comment #39
benjy commentedOK, I tracked the issue down to link fields with default values. They were being creating during the node migration and we needed to initialise $link['options']['attributes'] in FieldInstanceDefaults.
Attached is a patch that shows the problem with a test only patch, the interdiff and the final patch with the fix. The default settings don't seem to get stored in the field instance table in D6 so right now they're just initialised empty. Minor follow-up IMO.
Comment #42
gábor hojtsyThe patch fails with Schema errors for field.field.node.story.field_test_link with the following errors: field.field.node.story.field_test_link:default_value.0.options missing schema If you look at how field.field is defined in schema in field.schema.yml:
Then you'll look at field_config_base (in core.data_types.schema.yml):
I quoted the default value only because you have problems with that. So the default value is a sequence of options defined by the field_type on the top of the file (two levels up). So the fail may be one of a few:
1. the field_type is missing at the top of the file
2. the field_type is not what it should be (not the type that has an options key)
3. the options key should not be there because it is not supported by the (correctly specified) field type
4. the field type default value schema may be incorrect
Looking at link.schema.yml, field.value.link only supports a title and a url key, no options key. Unless this is incorrect, then 3. seems to be the problem.
Hope this helps.
Comment #43
gábor hojtsyReviewed this with @benjy in IRC. Looks like the schema is incorrect because there is an options key supported on the defualt items:
So the default value options key should support all options supported by Url::setOptions() (documented on the constructor).
Comment #44
benjy commentedAdded schema.
Comment #45
gábor hojtsyErm that is certainly not a string at least in PHP, its an object. Is it persisted as a string in config?!
Comment #46
benjy commentedNo it is an object, i'll take a look later and try to figure out what the schema should look like.
Comment #48
benjy commentedRemoved attributes and external.
Comment #49
gábor hojtsyAs for attributes, LinkWidget has this:
I think that it ALSO passes in 'attributes' as an option to Url is incorrect, right? Not that the default value may not have an attributes key, it may have. The typing of that is undefined though and left for others? How is this supposed to work?
As for the external flag, Url uses that internally in setUnrouted() so I guess it is not supposed to be set anymore? That means a link field will not be able to set an external URL anymore? Is that not what the link field is supposed to be about?
Comment #51
benjy commentedOK, brought attributes back and added schema. Interdiff is based on #44.
Comment #52
chx commentedI like it but I'll let Gabor RTBC it.
Comment #53
dawehnerAt least have a oneliner would be nice.
@benjy ensured that on the longrun we don't have to hardcode that in the LoadEntity class
Comment #54
benjy commentedThanks for the review, feedback addressed. I've created #2395993: Clean up Loadentity a little for the per field type processing discussion.
Comment #55
dawehnerThank you
Comment #56
gábor hojtsyStill not true. Same as I said in #45.
Comment #57
dawehner@Gábor Hojtsy
Ah interesting ... well I guess it used to be a string in D6. For D8 we have #2363459: Make it possible to provide 'langcode' in $options for URL generation instead of Language object to make it at least possible to just specify the string.
I don't even have a clue how you could insert the language in the UI?
Comment #58
hussainwebI am trying out two things from my review of the patch.
This is marked as string, but the documentation says this should be an array. We are not really testing this either. I have modified one of the test cases to check for this. If it passes, we should probably duplicate the test.
I checked the link-6.x module and different code paths in migrate but found no reference to language. I know it could be used later on in path processor. But I am curious as to what happens if we remove this.
Comment #59
hussainwebComment #60
gábor hojtsyI think its sort of fine to remove the language element for now, if we assume nobody will set it. Its much better than specifying an invalid value. Once somebody tries to set it at least in a test, they will get fails. Ideally we would figure out which keys are stored there and only allow those to be persisted (ie. filter based on the keys expected). Otherwise this may end up in hard to debug type mismatches in PHP for the options keys due to invalid config. That is not really the task of this issue though.
Comment #62
hussainwebForgot to update the test.
Comment #63
benjy commented@hussainweb, I don't see how adding that query string is offering any extra coverage? The only way query is used as part of the migration is for internal url's in the Route process plugin but this is an external url.
New patch just removes the language schema stuff as per #60, interdiff is based on my last patch in #54.
Comment #64
hussainweb@benjy: The reason I am testing for query strings is because query is an array. UrlHelper::parse is the function responsible for parsing URL's (both external and internal). You can see from the code (as well as documentation) that $options['query'] is an array. This function is called from PathValidator::getUrl() and you can see that $options['query'] does get assigned here.
However, in our new config, we are setting query as type: string. I wanted to test if actually testing a query part makes it fail. Apparently, it doesn't. It might be serialized before storage but I am not sure about that.
Comment #65
benjy commentedPathValidator::getUrl() does parse the query out but it's never used because of isExternal().
Adding a query to the data and testing for that here really makes no difference. The full url is saved when the link field is saved, the validator ignores the options when loading in LinkWidget because the url is external. If we really want to test how $options['query'] works, we'll need to add a relative url to the dumps and then a new test.
That said, the schema should as you said, indicate that query is an array not a string, new patch attached. Thanks for the review.
Comment #68
benjy commentedComment #69
chx commentedI think all the concerns have been addressed.
Comment #70
alexpottMigrate issues are excluded from the beta evaluation as long as they don't mess with the rest of core. Opened #2397233: Link module has untested features to store additional configuration per link to address the missing test coverage in the Link module found by this issue. Committed a5f0e6a and pushed to 8.0.x. Thanks!