Issue Summary
skip_on_empty's interaction with array sources that are considered 'multiple' values is poorly documented, and can be unintuitive.
Proposed resolution
Add documentation to the top of the plugin, that explicitly describes the expected behavior of skip_on_empty when dealing with multiple value sources, including when the source array contains 0 elements.
Original issue summary
I have been scratching my head over this issue. The source sometimes has a blank offers, sometimes it is an array with data and sometimes it is an empty array - like this one:
https://api.hel.fi/linkedevents/v1/event/matko:16134/
With the latter example I was getting a bunch of Array index missing, extraction failed exceptions from the extract plugin, despite the skip_on_empty plugin running before, as you can see from my yml file:
field_offers_is_free:
-
plugin: skip_on_empty
method: process
source: offers
-
plugin: extract
index:
- 0
- is_free
field_offers_info_url:
-
plugin: skip_on_empty
method: process
source: offers
-
plugin: extract
default: false
index:
- 0
- info_url
- fi
I managed - in the end - to track down the reason for why our skip_on_empty plugin was never called: When Get sets $multiple to true, processRow in MigrateExecutable runs a foreach on the value - causing the whole skip_on_empty plugin to be skipped.
With this quick fix in modules/migrate/src/Plugin/migrate/process/Get.php's transform function things work fine for me:
if (is_string($source)) {
$a = is_array($return[0]);
$b = !empty($return[0]);
$this->multiple = $a && $b;
return $return[0];
}
Did I encounter a bug in Migrate or should I change my migration configuration?
Issue fork drupal-2905929
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
hanoiiI just faced this same issue.
Comment #3
badrange commentedLately I have been wondering if it would be better to fix this by adding
handle_multiples = TRUEto SkipOnEmpty.In any case, I worked around this by creating my own Get implementation - and with this configuration.
Comment #5
colanCan someone else confirm that this is the same problem as Why doesn't skip_on_empty work when migrating from a text field??
Comment #6
damontgomery commentedI've had this same issue and it was driving me crazy.
When there is an empty array, skip_on_empty never runs the checks on the values and so it just keeps on keeping on.
Would it make sense to make a new plugin that checks the value / all values for a source and skips processing if it / they are empty?
Comment #7
StefanPr commentedIsn't extending skip_on_empty an option, instead of working around it not handling multiple values?
I'm not sure if it should be handling multiple values by default in core.
Comment #8
joelpittetRelated to this #2154209: Process refactor: multiple handling
Comment #9
colanI was running into this from D7 too, but I guess that's still not a priority. ;)
Comment #11
nadavoid commentedHere's the full code that I've just added to a project. Just one file at mymodule/src/Plugin/migrate/process/SkipOnEmptyMultiple.php.
I used the suggestion by StefanPr of extending SkipOnEmpty.
With this in place, you can use
skip_on_empty_multipleas a process plugin.Comment #12
makazimtingwa commentedskip_on_empty does not work for me when encountering empty arrays (ie, empty field). Say field_offers_is_free is a text field, the following migrate process throws extraction errors when the field is empty for a single node:
As of 8.5.6, this does work:
Similarly, if field_offers_is_free is a multiple-value field, the following works:
Comment #13
quietone commentedI ran into this today. I had completely forgotten about changing the source value to be an array and then I found this and the proverbial light bulb went off. Thanks to badrange posting this issue and to MakaziMtingwa for the example.
So this actually works as designed but I'm not closing it. Instead setting to NW for documentation, so this can into the handbook. This is also pertinent to skip_row_if_not_set, see https://www.drupal.org/project/drupal/issues/2615602#comment-12618383.
Removing the tag because this issue affects all migrations.
Comment #14
aleksipIf this works as designed, does that mean that core does not support skipping empty arrays? Or is there another way to do it?
Comment #15
colanAgreed. We should keep this open until there's a standard way to handle this.
Comment #17
edwardchiapetWe are migrating body field values (long text) into a paragraph entity, and saw an issue where paragraph entities were being created even when there were empty values.
We were able to solve this issue by using @nadavoid's custom process plugin to check for a value in the body field before processing it for the body field on the paragraph.
This is what our process configuration ended up looking like:
Here is the custom process plugin (simplified logic in ::transform()):
Hope this helps someone!
Comment #18
damienmckennaFYI I turned the code samples into patches for migrate_plus: #3054494: Alternative solution to skip_on_empty_multiple plugin from 2905929
Comment #19
woodseowl commentedThis definitely fixes the issue I was encountering with empty image fields being processed even though skip_on_empty was set. Same basic issue: if the source is an empty array, it seems to not get considered for whether to skip_on_empty. Thanks everyone!
Comment #21
oldspot commentedHad same issue with the normal 'skip_on_empty' plugin processing empty image field and this patch worked great.
Comment #22
codebymikey commentedRan into this issue, and I believe the SkipRowIfNotSet (
skip_row_if_not_set) plugin should work around this issue if you know what the index should be.I still think this is a bug and should be looked into.
Comment #23
kybermanIn my case, it was enough to use "single_value" plugin (migrate_plus) before calling "skip_on_empty". It's a switch to disable multivalue processing. There is also an opposite way available using "multiple_values" plugin.
Inside the example below, "migration_lookup" switches processing to multivalue, so if I need to check the whole lookup result is empty, I need to call "single_value" there.
Best regards
Vit
Comment #25
oldspot commentedI commented on this about 9 months ago when I used the 'skip_on_empty_multiple' process plugin which worked fine and since used it on another migration, but as I bumped into the same issue again on a new migration I re-discovered this issue and decided to try the 'single_value' plugin solution explained above and works perfectly.
Means we don't have to include another patch on the site and make use of what's already available (or at least provided by the 'Migrate Plus' module).
Comment #28
rob230 commentedI think I have the same problem, or a similar bug.
I am trying to migrate from a field_test/0/format, which has no value in the source database, but it is somehow getting a value of 'basic_html' in the migration. I don't even understand how, it makes no sense.
If I have this migration:
Results in this output:
As you can see, some values are clearly NULL.
When I change the migration to this:
The output is this:
Everything is getting set to 'full_html'. So the only conclusion I can come to is somehow the skip_on_empty plugin is creating a value out of nowhere, or using the value from the previous row. Seems like a bug to me.
I tried inserting single_value plugin above skip_on_empty but it made no difference.
Comment #29
makazimtingwa commentedRob230... from your output, it appears to be working. The final output has skipped #5 & #6 due to the skip_on_empty and proceeded to #7 & #8.
Comment #30
rob230 commentedYes the migration is actually working. I stepped through it in a debugger and skip_on_empty worked correctly.
My problem was something else. The field settings have a default value of:
So when the migration says that field has no value Drupal is setting it to the default.
Comment #34
nickdickinsonwildeMR opened but I'm concerned that it does need tests and backwards compatibility might be an issue.
if BC does count as an issue:
- easy fix IMO would be to add optional flag to treat it as handles multiple and otherwise use a foreach loop internally.
Tests are semi-blocked based on whether BC needs to be handled
Comment #35
joachim commentedChanging the title to be clearer; adding parent issue.
Comment #36
joachim commentedOne way in which this is a problem is that you can't use it to check that a migration lookup returned something.
E.g.
That won't work, because migration_lookup returns an empty array if nothing is found. And skip_on_empty will carry on going (in fact, it won't even be called).
Comment #37
vinodhinisureshbabu commentedI managed to import the multi value paragraphs without throwing empty array errors using a callback method - array_filter
Here field_section_two is the paragraph field
Comment #39
mikelutzAs a reminder, and as @quietone pointed out in #13 this is not a bug, and we cannot change skip_on_empty to handle multiple both because of BC reasons, and because it would then not work as it is supposed to. This issue is about improving documentation on how skip_on_empty interacts with arrays and sources that contain multiple values. skip_on_empty is not supposed to skip empty multiple arrays, nor does it need to for any of the standard core migrations. If you think you need that for a custom pipeline, most likely your pipeline is not structured correctly, and in the rare case there might be a legitimate reason to need to do this, there are a number of workarounds using popular contrib plugins, such as single_value from migrate_plus.
Comment #40
mikelutzOne concern I have about adding documentation around this inside the plugin is that none of this information really has anything to do with the plugin itself. The concepts here are with regard to multiple vs single values in pipelines, and how multiple and single values interact with plugins depending on whether the plugin declares handle_multiples: true. If we add documentation along the lines of "Skip on empty does not operate on an entire value if the pipeline is in 'multiple' mode. In this case, skip on empty will be called once for each element in the array. If a 'multiple' value contains no elements, skip on empty won't be called, and the pipeline will continue with the empty multiple array", We are just describing how plugins that don't `handle_multiples`. The exact same documentation would be applicable to all 58 non-multiple plugins in core. I feel like the plugin's documentation should focus on what the plugin does. What it's outputs are given an input and what the configuration options are. With an empty 'multiple' array this plugin and any other single plugin is never expected to be called. I'm not convinced that one random single plugin header is the correct place to add that documentation.
Comment #41
danflanagan8I think that's a bit harsh :) , but I agree with everything else in the previous two comments from @mikelutz.
In light of comment #40 I would recommend closing this issue in favor of #2961560: Document usage of 'handle_multiples' in process plugins.
Setting to NR to get input on that approach.
Comment #42
mikelutzFor the record, I followed that comment with the correct workaround in the rare cases it's appropriate, but I definitely stand by it. There is almost never a need to halt processing on a empty multiple source value with core or migrate_plus plugins. Any non-mutiple plugin isn't ran and all multiple plugins should handle that case, and core entity destinations can handle empty arrays as easily as nulls, so it's rare you would need to convert an empty array to a null outside of very custom plugins.
Comment #43
texas-bronius commentedI think @vinodhinisureshbabu in #37 poses a nice solution:
```
field_plaintext_author:
- plugin: parse_lcm_author
source: body/0/value
- plugin: default_value
default_value: 0
- plugin: skip_on_value
value: 0
method: row
```
My plugin `parse_lcm_author` returns an array of values (or empty array `[]`), and `default_value` understands an empty array to require a default value. I used 0 but any scalar value will do. (skip row or process as is appropriate to your needs -- I'm building paragraphs in this migration, so it was good to get out without a mapping made)