When we implemented #2937717: Add support for manipulating a complete 'feeds item'/'row'. we did not solve at the time the issue of how plugins can be aware of the available item when being configured via a UI.
Original report
When configuring a Tamper plugin, there is no Item available. So there is currently no way for Tamper to know which sources are available (the Feeds Tamper's 'rewrite' plugin needs this information). Maybe Tamper plugins needs an extra method to set the available source field names. This problem can be dealt with in a follow up, but I mention it as it is closely related.
One pattern I have seen when investigating contexts is the use of FormStateInterface::setTemporaryValue and FormStateInterface::getTemporaryValue
For example, the BlockForm module calls $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); and the condition plugin base calls $contexts = $form_state->getTemporaryValue('gathered_contexts') ?: []; to be able to access the available contexts with the plugins sub form.
We could implement a similar pattern, however we need to make sure this is document / obvious for both plugin developers and modules like feeds tamper that need to supply the available values.
Example:
In feeds tamper UI - TamperFormBase
// Current code
$subform_state = SubformState::createForSubform($form[self::VAR_PLUGIN_CONFIGURATION], $form, $form_state);
// Additional pseudo code
$available_source_properties = $this->feedsFeedType->someMethodToGetSources();
$subform_state->setTemporaryValue('available_source_properties', $available_source_properties);
// Existing code
$form[self::VAR_PLUGIN_CONFIGURATION] = $this->plugin->buildConfigurationForm($form[self::VAR_PLUGIN_CONFIGURATION], $subform_state);
Inside a plugins form
$form['source'] = array(
'#type' => 'radios',
'#default_value' => $this->getSetting('source');
'#options' => $form_state->getTemporaryValue('available_source_properties'),
'#title' => t('Source'),
);
| Comment | File | Size | Author |
|---|---|---|---|
| #26 | tamper-source-definition-2971881-26.patch | 40.25 KB | megachriz |
| #22 | feeds_tamper-source-def-2971881-22.patch | 5.15 KB | megachriz |
| #17 | tamper-rewrite-2971881-17.patch | 4.14 KB | megachriz |
Comments
Comment #2
ericgsmith commentedI intend to sit on this for a bit to think about the pros, cons and an alternative.
Thoughts, suggestions and criticisms welcome.
Comment #3
ericgsmith commentedComment #4
jamesdixon commentedForgive my beginner's attempt at understanding here. :)
Right so if some plugin wants to use tamper to modify sources of an item/row, right now the developer of the plugin would need to write their own custom code or hard code the sources into a UI (if they need a UI). This is because we are not processing items during configuration.
If that's the case I agree setting something like this up would be useful.
Could be used to grab the sources outside of the actual processing step from external plugins.
These external plugins could use set and get temporary value to store the sources for use outside of processing.
Is this what you mean?
Comment #5
ericgsmith commentedYes, so imagine you want to configure the copy plugin. The copy plugin will allow you to specify if you want to copy the tampered value from or to another item.
So in your feed row / item you might have:
$item->id = 'foo'
$item->name = 'bar'
$item->zip = 'zap'
If we want to use the copy plugin to copy the value of the source property zip onto the source property of id, we have 2 options for the form UI when we configure the tamper plugin.
Least desirable - is that the source field is a text field, which forces the user to know what sources are available on the feed.
Most desirable - the plugin / form can be told about the available source properties of the feed, and can offer the user a select list of the sources to configure the plugin.
To accomplish the most desirable outcome, we need a clean and easily understood way in which the UI (feeds tamper) and pass the available source properties (from the feed item) into the plugin so that the plugin can present the available options to the user.
Comment #6
jamesdixon commentedAh okay totally makes sense.
Do you think
Should return the sources in a format a form '#options' would understand, so they could simply drop the result into a 'radios' or 'select' form? Is selecting sources from these form types the main use case, or do you see any other ways people would need to access the sources from a plugin?
Comment #7
ericgsmith commentedYes, at this stage the only use can I can think of is the form - as in the only other public method we already have a real item.
Attached is a patch showing how this could work. I'm not hugely sold on the fact this is hidden behavior as far as the implementer is concerned.
Comment #8
ericgsmith commented*edit* oops accidentally uploaded the patch I just tested
Comment #9
megachrizI see these possible options to make passing sources more obvious:
buildConfigurationForm()to pass in additional context wouldn't work as you then break the interface?Comment #10
ericgsmith commented1 - Agree on this, an exception would be useful to explicitly state what is required to an implementer, and allows them to be in control of how to handle that result.
2 - I'm not a fan of this, as the available sources are not configuration or plugin definition - in other contexts they could vary of any given instance. It also introduces a level of complexity that not all plugins need, as only a small subset currently use the tamperable item.
3 - Still my preferred approach long term, but I don't have the time to follow this through myself. Happy to revisit this for an 8.2 branch on the assumption we want to get this 8.1 branch stable / released as soon as possible.
4 - I guess we could implement our own interface instead of PluginFormInterface, but I would prefer not to so that we are keeping inline with any a core examples and practices where possible.
Attached is an updated patch on how we could use approach 1.
Comment #11
megachrizAn other idea, similar to idea 2 from #9:
createInstance()method. Inside$configuration, expect 'available_source_properties' to be set (else throw an exception).$configurationbefore instantiating the plugin.I did something alike in a project where the base plugin class did not extend \Drupal\Component\Plugin\PluginBase at all and had a complete different set of constructor parameters.
https://github.com/MegaChriz/afasprofit/blob/3.x/src/Core/Entity/EntityF...
It may not be the best approach, but the advantage is that Tamper plugins then would have access to the available sources list outside of
buildConfigurationForm()as well.Comment #12
ericgsmith commentedIts been a long time since I've thought about this as I've been on a few non Drupal projects, however coming back with some fresh eyes to give it another look.
I'm not convinced that we should be adding an extra param to the constructor or to the plugin at all for the available sources. For my mind, the plugin structure has enough information already to work for it's purpose - the only thing that is missing is that it can't be configured through the UI (which is only one avenue to configure a plugin). I don't think we need any additional information stored on / in the plugin in order for them to function correctly.
I can also see plugins being configured once to work on a variety of items, where the available sources could be completely different for each item passed it, but a plugin may still expect to exists as a single instance that works consistently.
I'm keen on getting this sorted without having to change any structure on the plugin - but would be happy to rethink if we can think of a reason why the plugin would need to know the available sources if it has been configured properly.
In my mind, the solution should be within forms, either
- using temp form storage
- using a custom sub form state and offering a trait or base class for modules to use when configuring plugins
The main goal is that its immediately obvious for plugin authors how to get the sources if needed, and also obvious for any modules implementing plugins. I'm going to think about this a little longer before doing any more dev experimentation on it.
Comment #13
megachrizA type of plugin that uses an extra parameter in the constructor is "CommerceCheckoutPane".
::create()method for a CheckoutFlowInterface. For::create()the parameter is defined optional in order to comply to the interface \Drupal\Core\Plugin\ContainerFactoryPluginInterface.\Drupal\Component\Plugin\PluginManagerBaseand\Drupal\Component\Plugin\Factory\FactoryInterface..The plugin type "FieldFormatter" uses a slightly different approach.
FormatterBase::__contruct()requires a instance of FieldDefinitionInterface.FormatterPluginManager::createInstance()expects a FieldDefinitionInterface as part of the configuration, defined with the key 'field_definition'.FormatterPluginManager::getInstance(), however the options array also expects to receive a FieldDefinitionInterface.EntityViewDisplay::getRenderer()finally passes this FieldDefinitionInterface to the plugin manager.My point is that I think it is a approach that is proven to work.
So I think we should come up with a SourceDefinitionInterface and a SourceDefinition object that consumers can use to pass to the Tamper plugin.
Comment #14
megachrizThis is how a SourceDefinition implementation with Tamper would look like.
Comment #15
jamesdixon commentedI plan on reviewing this in the week coming up.
Comment #16
jamesdixon commentedSeems like the approach in #14 would work.
Would it be possible to make the source definition optional for plugins which aren't using the source definitions in any way?
Or is the idea we always need the source definition so other modules working with the tamper plugins have an idea of the sources for form building etc?
Comment #17
megachrizThe idea is that other modules always need to pass a source definition, because they won't know beforehand whether or not a Tamper plugin requires that information.
Attached three patches:
Comment #18
ericgsmith commentedMassive thank you for progressing this! Its looking great from a first look.
Very happy to take your lead here on this for this approach.
Comment #19
jamesdixon commentedComment #20
megachrizLast week @andileco asked during the Feeds meetup what work is left for this issue.
Basically, the patch in #17 looks good to go. The reason that I didn't commit the patch yet is:
I tried hard to think about a better approach, but my mind was going in circles there. Maybe we should accept the less perfect solution, so we can move forward. And perhaps I'm wrong about fearing that my solution makes integrating Tamper too hard.
Comment #21
andileco commentedI added the rewrite patch that's part of #17 to the appropriate issue: https://www.drupal.org/project/tamper/issues/2976180
Comment #22
megachrizI've taken a look at my code in #17 and investigated parts I didn't like:
unset($configuration['source_definition']);create()method:The CountryToCode plugin then had to define that extra parameter as an optional one in order to still comply with the interface "ContainerFactoryPluginInterface":
Field formatter plugins in Drupal Core approach
So I've taken another close look to how Drupal Core has organized code for field formatter plugins. FormatterBase requires an instance of FieldDefinitionInterface, so its situation is in that regard similar to the approach I had taken.
I see that TimestampFormatter for example just expects that the field definition is passed as part of
$configuration:So in TamperManager, passing the source definition as part of
$configurationsounds like a better approach to me. And it eliminates one occurrence ofunset($configuration['source_definition']);as well.TamperPluginCollection
I already left a
@todoin that class to investigate if an instance of it should require a source definition. I tried how that worked out and I think this made the code in Feeds Tamper a bit cleaner:And guess what, it eliminated another occurrence of
unset($configuration['source_definition']);;).So that concludes my motivation for the changes I had made in this new patch. It might not solve the (potential) issue that it will get harder to integrate Tamper in other systems, but I think that the implementation is much cleaner. Now I hope it will pass tests. At least all Feeds Tamper tests pass locally.
Comment #24
megachrizAh, I forgot to update SprintfTest and I missed updating TamperConfigSchemaTest as well.
New patch. Also fixes some coding standard issues.
Comment #26
megachrizHehe, referenced the wrong thing in TamperConfigSchemaTest.
Comment #27
megachrizHide some files, so the most useful patches show up on the top of this issue.
Comment #29
megachrizCommitted #26, with some slight changes in the code comments.
Now onto the rewrite plugin in #2976180: Rewrite Plugin! And adapting Feeds Tamper of course.
Comment #30
megachrizAnd opened follow-up for Feeds Tamper: #3061554: Pass new SourceDefinition to Tamper plugins.