Problem/Motivation
The text format configure form contains the forms of all of its filter plugins.
The block entity form contains the form of its block plugin.
The view entity contains the forms of its displays, rows, styles...
Condition plugins currently use FormInterface, but that is intended for a standalone form being passed to drupal_build_form().
Proposed resolution
Introduce a new interface similar to FormInterface, but without getFormID().
This should not be explicitly tied to plugins, but they will be the main use case for this.
Remaining tasks
Will there ever be a case where a form is to stand alone or be used as a sub form?
If so we'll need different method names, which we might want anyway.
Possibly consider a better name than SubFormInterface.
User interface changes
N/A
API changes
Addition
Related Issues
Comment | File | Size | Author |
---|---|---|---|
#27 | form.multi_.27.patch | 22.24 KB | sun |
#6 | embeddable-2006248-6.patch | 1.54 KB | tim.plunkett |
Comments
Comment #1
EclipseGc CreditAttribution: EclipseGc commentedBasically +1000000000 to this. I think we want to bring the various plugins in line here. Blocks is a really big offender here doing it's own thing. I think actions does something similar in the patches tim has in the queue, and Conditions outright implements FormInterface. We need to be able to say a plugin is expected to render a form in the UI, and allow the wrapping code the provide the presentation of that form as well as the buttons to submit/cancel/etc it.
Eclipse
Comment #2
amateescu CreditAttribution: amateescu commentedI would need this as well for "entity subforms" (or inline entity forms).
How about FormFragmentInterface?
Comment #3
andypost+1 to idea, that would be helpful for menu links and path elements. And nice example is
menu_overview_form()
About naming - Fragment is a part of smth but here we dealing with form a whole but Embeddable into other form
Comment #4
EclipseGc CreditAttribution: EclipseGc commentedok, yeah I like EmbeddableFormInterface.
Eclipse
Comment #5
tim.plunkett+1
Comment #6
tim.plunkettJust a start.
Comment #7
fagoI totally see the need for something like this, however this solution won't work for entity forms. See #1728816: Ensure multiple entity forms can be embedded into one form.
While I think this is a reasonable and straight-forward approach, I wonder whether it makes sense once we need something entities as well? So maybe we should just make entities fit into this approach also... the difficult point being there is altering.
This method names seem to be quite a bit verbose. Couldn't we just go with validateForm() as usual?
Comment #8
yched CreditAttribution: yched commented+1 on something like this being needed so that EntityFormControllers can by used at several places in a form, to support "several entities in a form". This is pretty important since that's what field_collection relies on, for example.
One challenge for "SubForm" objects is that they need to cluster $form_state in separate, isolated entries, so that the business data they place in $form_state doesn't overwrite the stuff placed by the other SubForms - which is what Field API had to do in #942310: Field form cannot be attached more than once to support "fields of several entities in one form" in D7 (see field_form_get_state() / field_form_set_state()). I guess helper getMyOwnFormStateBucket() methods in a base implementation would come a long way make that logic workable.
Comment #9
tstoecklerIn order to make e.g. entity forms embeddable couldn't we just do something like
That would also evade the subFormValidate() method naming stuff.
Comment #10
amateescu CreditAttribution: amateescu commentedI also had this thought at first, but I don't think we can do that because
EmbeddableFormInterface
will probably need a method to get the parent form state, and such a method wouldn't make sense to have onFormInterface
.Edit: Hm.. if $form_state is passed along all over the place, maybe that method will not be needed after all.
Edit 2: Nevermind, what @yched said :)
Comment #11
tstoecklerWell, buildForm(), validateForm(), and submitForm() (which are the method names I should have used in #9) all get $form_state passed along. Which makes sense, IMO.
So at least conceptually that should not be a problem. Practically, I agree with @yched that passing only a subset of the actual form state or merging the different states or whatever is not going to be trivial to implement for such a generic system.
Comment #12
tim.plunkettI have not thought about the $form_state implications yet, going to let someone more knowledgeable with the problem space work that through.
The reason to not repeat method names are if we want a single class to serve as a standalone and embedded form. If you make the names the same, you can't implement both interfaces.
Comment #13
tstoecklerWell if we make the interfaces extend each other like I tried to explain in #9 you could do exactly that. Don't know if I've missed anything else major about that, though.
Comment #14
tim.plunkett#2033383: Provide a default plugin bag isn't a solution for all of this issue's problems, but it addresses one of the main use cases.
Comment #15
Xano#1938992: Use EmbeddedFormInterface to embed form snippets in forms might try to achieve the same thing.
Comment #16
XanoWe may be able to make stand-alone forms embeddable, which sounds useful and extremely cool, but I'm trying to think of use cases where someone absolutely does not want a form to be embeddable at all.
A few more reasons to get this issue fixed in Drupal 8:
drupal_get_form()
to the point where it introduces some annoying side effects just to embed form snippets.#process
callbacks in Payment, just to be able to pass on$form_state
to plugin's embeddable form builders.I'm adding the DX tag here, because this issue aims to solve a lot of existing WTFs and custom fixes to the same problem.
Comment #17
XanoWhat if we add a
FormSnippetInterface
that contains build, validate, and submit methods and letFormInterface
just keep itsgetFormId()
method? This would not break BC.Alternatively, for consistency with
BaseFormIdInterface
, we can renameFormInterface
toFormIdInterface
and still let it implementFormSnippetInterface
, but that will break BC.We have a few good ideas to solve building embedded forms, as well as for storing data in
$form_state
. However, what do we do with keys like$form_state['rebuild']
and$form_state['redirect']
? I imagine that form snippets need to be able to tell the form to rebuild, but not redirect it?Comment #18
tim.plunkettHow is this even remotely on the table for D8?
Comment #19
XanoBecause it doesn't have to be an API change per se? Also, some API changes still make it in. This obviously would be one that fixes core WTFs.
Comment #20
bojanz CreditAttribution: bojanz commentedHaving this would remove most of the Inline Entity Form code, a module with 15k installs and a very obvious use case (creating entities as they are being referenced). +1 to whatever, whenever.
My biggest problem right now is that IEF needs to simulate the form processing and call validate and submit methods manually and recursively.
Comment #21
BerdirMore importantly, one of the functions that you are calling recursively is field_attach_form(), which #2095195: Remove deprecated field_attach_form_*() wants to remove. So you'd have to duplicate even more code. Might actually be a blocker to remove those.
Comment #22
bojanz CreditAttribution: bojanz commentedYes, if field_attach_form dies I'm screwed.
Comment #23
XanoAren't field form elements attached to entity forms automatically now?
Comment #24
BerdirOnly if you call it automatically that ContentEntityFormController calls field_attach_form() for you.
And that is actually my point. If ContentEntityFormController is the only way to get that automatism, then we leave modules like Inline Entity Form out in the cold. See field_test_entity_nested_form() for a core example, it's right now impossible to port that to use an EntityFormController.
Comment #25
yched CreditAttribution: yched commentedf_a_form() only deals with configurable fields, it has no reason to stay the moment we start using widgets on base fields too - I mean, IEF does need a fuction/method to be able to do its job, but just acting on configurablw fields is meaningless.
Comment #26
tim.plunkettTo be clear, I definitely realize we have a problem that needs solving.
But
FormSnippetInterface
andFormIdInterface
do not sound like it solves this problem, and just makes our interfaces worse.Comment #27
sunThere's one minor detail but major part in the proposal which I don't agree with:
By design, the answer is "Yes." — Here's why:
The one and only point of an
EmbeddableFormInterface
would be to distill an embedded form from its parent form. It is defined separately, and just happens to be contained elsewhere. The challenge is to figure out (1) whether an embedded form exists, (2) to whom or what it belongs, and (3) dispatch processing of the sub-form in a controlled way.→ The art of Addressing - or - [reverse-]mapping a data object to a provider. That's the whole point of
$form_id
.Identifying a subform within a larger form would theoretically also be possible by turning the top-level
$form
array into an object, but that is not possible for D8, since it has wide-ranging consequences that would impact the entire rest of the system; specifically, it would breakNestedArray
anddrupal_render()
.The aspect of being embeddable does not mean that the form cannot be used on its own. A very typical use-case for using an embeddable form in a standalone way are configurable plugins — it would be pointless to e.g. build the full text format form, just to be able to extract the embedded form of a certain filter plugin.
The scope of the challenge of embedding a form into another is limited exclusively to
FormBuilder
. Here are the main tasks that need to be addressed:#type 'form'
in a form.#parents
.$form_state
as if it was a standalone form.$form_state
as if it was a standalone form.Attached patch is a first prototype and proof of concept, based on Test Driven Development. The unit test proves that the processing of the parent form and the embedded forms does meet each form's individual expectations.
Needless to say, I had to hack my way through
FormBuilder
to make this possible. Please do not interpret the concrete changes in this patch as an officially proposed implementation — it's an eye-opener only.The most limiting factor was that
$form_state['build_info']
is not "scoped" (sub-keyed) to a particular$form_id
. Problems already start inFormBuilder::getFormId()
, which injects theFormInterface
instance into$form_state
→ You cannot callFormBuilder::getFormId()
for another$form_id
using the same$form_state
.And of course, there are a whole range of architectural questions that need to be addressed; each form gets input and values that are scoped to the form, that is out of question. However, what about the remaining
$form_state
? How much is or should be shared between the parent form and the child forms? Can anything be shared in the first place, or will that cause unforeseen name-clashes of keys in$form_state
? Just think of$form_state['entity']
... → In this PoC, I opted to selectively share$form_state['storage']
only.FWIW, this PoC has the additional potential to eliminate and replace the utterly wonky "extract form values" code of field widgets in the Entity Field API. → From a technical perspective, a field form widget is a subform that gets built, validated, and submitted on its own.
Let's discuss. :)
Comment #28
sunComment #29
yched CreditAttribution: yched commentedNo time for a fully thought out comment here, but see my comment in #2095195-43: Remove deprecated field_attach_form_*(), about why EntityFormController is currently very much a full fledged, standalone form, and cannot be used for a nested subform.
Comment #30
jibranargh I always hate it. so please add comment.
doc block missing.
white space.
revert please.
Comment #31
tim.plunkettWhy would only non-injected forms get the form builder?! And why would they get it at all?
It's not clear to me why we need this change.
There are not nearly enough comments here.
Comment #36
andypostComment #37
andypostComment #38
EclipseGc CreditAttribution: EclipseGc at Acquia commentedSubformStateInterface landed in core long ago and solved most of the technical problems associated with this. Closing.
Eclipse
Comment #39
bojanz CreditAttribution: bojanz at Centarro commentedThis is about embedded forms, not about form state.
Comment #40
EclipseGc CreditAttribution: EclipseGc at Acquia commentedI realize that, but given that Plugins would have been the primary beneficiary of this sort of code and that the SubformStateInterface seems to have totally solved its needs, I'm unconvinced that having a separate classification of form type for embedding is worthwhile since the SubformState is the part of that equation that needed solving.
Disagree?
Comment #42
geek-merlinYes, some APIs have been built without this, but it's a PITA:
* \Drupal\Core\Condition\ConditionPluginBase::buildConfigurationForm
* \Drupal\Core\Condition\ConditionPluginBase::validateConfigurationForm
* \Drupal\Core\Condition\ConditionPluginBase::submitConfigurationForm
* Also \Drupal\media\MediaTypeForm::getSourceSubFormState juggles around naked form arrays.
So let's be nice to the remaining kittens and still do this.
Comment #49
geek-merlinCurrently working on something like this in IEF.
Patch #6 by tim.plunkett is exactly what i thought of too.