Except for the node form which has hook_node_submit() current entity forms in core cannot be extended in a clean way, such that a form element for editing a custom entity property is added (without using the field API).

Additionally some core entities implement $form['#builder_function'] - thus reusing this function for building the entity after a submit is possible. This feature is only needed in order to use the entity form as part of a wizard or split it up into a wizard-like form - regular form rebuilds work fine without it. Therefore I think the current situation of having not all entities support is fine, whereas of course having this opportunity in any entity form would be nice.

-> Thus I don't think the second point is something that is important to fix, but I'd say the first is a must. As I think Drupal 7 really should have properly extensible entity forms, I add the major tag.

To start the discussion I try to summarize my thoughts on the first point.
Possible ways to make entity forms extensible:

  • Introduce hooks.
    Just as the node form does. Add hooks for validation + extracting form values (hook_entityType_submit).
  • Rely on FAPI
  • As described in #735800-92: node form triggers form level submit functions on button level submits, without validation FAPI lacks a way of invoking the form default behavior. When a button overrides validate + submit it could need way to invoke the overridden default behavior. That way instead of implementing a hook modules could just add their validation + submit stuff as regular form validation/submit handler. This works in particular fine for #validate, but in order to work fine for #submit too the entity submit phase needs to split up into two steps/submit handler: One for updating the $entity in form state and one for actually saving the entity. Ideally the latter is invoked via a button submit handler, so the form default submit handlers are just caring about updating the entity - thus they could be easily reused. For that a function just invoking the default form submit handlers could serve as #builder_function.

    This is also similarly implemented by the node form for #submit, but not in a very clean way.

  • Provide both ways
    We could also provide both ways at the same time.

Personally I tend to think the FAPI option is the way to go.

Disadvantages:
>Hooks:
* If we add hooks for it, there are still the FAPI #validate and #submit handler which could be partially used too. Thus we are just adding another possibility to do it - one that is working properly for any case though.
* Hooks act globally on entity level. However entity forms might not be extended only in some cases, thus even if the added form element is not there, the hook is invoked - which is no problem, but not very nice. If an entity starts to do several different kind of forms though, this becomes confusing.
* What's the point of having a hook when the actual action to be taken acts on the form level?

>FAPI:
* Using form-level #submit handlers for updating the entity and only because of that moving the handler for saving to the button level could maybe be called unclean?

>Both:
Having both ways is probably just confusing people.

Advantages:
>FAPI:
* Only one way to do it. The less ways there are, the less people are confused.
* Providing a way to invoke default FAPI handlers would be a nice improvement for non-entity forms too.
* When we have FAPI file inclusion support as done by #561226: Forms (elements) need to able to specify include files to be loaded for building, the function callbacks could be easily put into any include file. That's not so simple for hook implementations (we could just add one fixed file 'group' for the hooks via hook_hook_info()).

Comments

rfay’s picture

subscribe

yched’s picture

subscribe :-)

sun’s picture

We need to do both 1) and 2).

And as long as there is no new Form API facility that would allow us to bundle/group any entity submit/update callbacks to ensure at least some sanity in the order of #validate and #submit functions, we should continue the hook_ENTITY_submit() pattern. At least for D7.

effulgentsia’s picture

Using form-level #submit handlers for updating the entity and only because of that moving the handler for saving to the button level could maybe be called unclean?

Yeah, I still find this somewhat suspect, and am concerned about rushing something like that into D7. Currently, we only do this for node forms, and that's cause that's how we did it in D6, but I'm not convinced yet this is a pattern we want to spread. Note, I'm not opposed to adding a form_execute_default_handlers() function per se, but I'm not so sure we want to make the updating of an entity be the form-level #submit. Please see Barry's comment and my reply about this issue of using a form-level #submit in this way. For D8, we can have time to think about things like #pre_submit or #default_submit, but changing the conceptual relationship between form-level #submit and button-level #submit for D7 doesn't sit quite right with me.

Therefore, I think my preference is hook_(comment|user|taxonomy_term)_(validate|submit)(), though I agree that this is unfortunate, because since multiple forms can be created for the same entity type, and even the main form can be altered, it means every implementation of such a hook will need to have an if statement that checks whatever assumption about the form it needs to check, as menu_node_submit() does.

I'm definitely still open to other ideas, if we think they're do-able for D7.

effulgentsia’s picture

Issue tags: +D7 Form API challenge

I'm adding the FAPI challenge tag to this, because maybe the right solution will be to add more FAPI properties to have finer control of validate|submit handler execution. Probably not, but maybe.

Frando’s picture

What about making #builder_function be an array?

Then forms that extend an entity form and add a custom property can add their builder function in their form_alter:

$form['#builder_functions'][] = 'my_custom_builder_function';

In my_custom_builder_function(), the module would set its properties in $form_state['node'] based on $form_state['values'].

In node_form_submit() and node_form_build_preview(), in place of
$node = $form['#builder_function']($form, $form_state);
we'd have

form_execute_builder_functions($form, $form_state);
$node = $form_state['node'];

and in form.inc

function form_execute_builder_functions($form, &$form_state) {
  foreach($form['#builder_functions'] as $builder_function) {
    $builder_function($form, $form_state);
  }
}

node_form_submit_build_node() can stay as is now, except it wouldn't return $node anymore.

That would be an API change, but we changed the API of #builder_function a couple of days ago anyway, and I don't think it's used a lot in contrib as of now.

fago’s picture

ad #4:

Agreed. Both ways aren't ideal yet, but I don't feel like introducing a new FAPI property is the right way to go. Having a #validate + a #submit makes sense. Ff we need an extra step for entity forms we should implement it there, but not generally on FAPI level (if there is no cause to do so).

ad #6:
I like this idea.

I'd vote for removing _function, as we its an array of callbacks now just as #submit and #validate. Thus #builder should be fine. But note - as #builder needs to get manually invoked, it's no real FAPI property. Therefore form_execute_builder_functions() should be something else, e.g. entity_form_build_entity() whereas we could add the existing entity_form_submit_build_entity() there as entity_form_default_builder() or such? If we want, we could even support BC for #builder_function by pointing it to entity_form_build_entity(). But with #builder I don't see a need for #builder_function, as I don't think it's used anywhere I'd vote for dropping it now.

effulgentsia’s picture

Status: Active » Needs review
StatusFileSize
new815 bytes

I could go along with #6/#7 if someone wants to roll that patch. I think the basic premise here is that form-level #validate is already what we want. It's not often there's button-level #validate, and if there is, and if it wants to invoke the form-level ones before or after calling its own, that makes conceptual sense. I think what I'm stuck on is making the form-level #submit of entity forms incomplete by only being the thing that builds the entity, but not doing anything with it: other than node forms, all our forms have form-level #submit that does the complete primary action of the form (i.e., for entity forms, saving the entity), and then we use button-level #submit for all the "other" buttons, but not the "primary" one. And that's why we have a new property like #builder_function, or whatever we may want to change that to, for building the entity independent of what we then do with it.

While we're still in the brainstorming phase, here's another option to throw into the mix. Leave #builder_function alone (if we care at all about BC here) to be used by the module that defines the form, and add a new #entity_submit property for modules that implement hook_form_alter() to extend the form. See patch.

sun’s picture

Good ideas. I'd vote for #entity_submit, using Frando's proposal of functions to invoke, i.e., #8 and #6 combined, merging #builder_function into #entity_submit.

moshe weitzman’s picture

Anyone available to push this along?

fago’s picture

StatusFileSize
new2.97 KB

ok, here is a patch the makes the comment form extensible, so we can discuss the approach on the example of comments.

Attached patch:

  • doesn't change the API (except for introducing new entity form guidelines)
  • Introduces #builder for defining build handlers. As the place of entities in $form_state varies we need to pass the entity to the #builders. This is done by the #builder_function of the entity. I like the name #builder more as this entity build could be also invoked during #validation by an entity, therefore I'd prefer to not call it #*submit.
  • Improves the docs of entity_form_submit_build_entity() to explain that.
  • The patch doesn't convert comment_submit() to another #builder, which is not necessary to make the form extensible. Instead comment_submit() is just invoked manually at the end of the #builder_function.

Opinions?

fago’s picture

Here is an updated patch that merges invoking #builder into entity_form_submit_build_entity() - just as #8 does. That way the helper does everything properly and modules just need to invoke entity_form_submit_build_entity() from their #builder_function, passing the edited entity to it.

fago’s picture

StatusFileSize
new2.13 KB

and the patch...

fago’s picture

Opinions on #13? We should fix this rather soon such that contribs finally can follow the core-pattern for implementing entity forms.

sun’s picture

+++ includes/common.inc
@@ -6709,6 +6714,13 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
+    foreach ($form['#builder'] as $function) {

I guess that works - but #builders absolutely needs to be plural.

Are all core entities invoking entity_form_submit_build_entity() already?

Powered by Dreditor.

fago’s picture

StatusFileSize
new2.13 KB

>Are all core entities invoking entity_form_submit_build_entity() already?
Yep.

>I guess that works - but #builders absolutely needs to be plural.
Makes sense.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to go for me.

yched’s picture

Status: Reviewed & tested by the community » Needs work

Sorry, minor :

+++ includes/common.inc
@@ -6687,15 +6687,20 @@ function entity_form_field_validate($entity_type, $form, &$form_state) {
+ * not field data, and calling field_attach_submit() to copy field data. Also
+ * this helper invokes any additional, in $form['#builders'] specified builder
+ * functions.

typo / missing word ?

47 critical left. Go review some!

fago’s picture

Status: Needs work » Needs review
StatusFileSize
new2.16 KB

ok, I simplified this sentence.

yched’s picture

Status: Needs review » Reviewed & tested by the community

Thks, back to RTBC.

marcingy’s picture

Priority: Normal » Major

Changing to major as per tag.

mustanggb’s picture

Tag update

fago’s picture

#19: extensible.patch queued for re-testing.

fago’s picture

Any update on this?

fago’s picture

StatusFileSize
new1.85 KB

The trailing whitespace has already been removed. Re-rolled the patch without that whitespace fix.

sun’s picture

+++ includes/common.inc
@@ -6738,6 +6743,13 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
+  // Invoke all specified builders for copying form values to entity properties.
+  if (isset($form['#builders'])) {

Coming back to this issue after a while, I'm still happy with this patch, but I first wondered

"ok, what's a 'builder' again?"

"...and, is that a Form API property, or to which API/module does it belong?"

If that other property wouldn't be called #builder_function already, then I'd really have to insist on proper namespacing, i.e., #entity_builders.

Powered by Dreditor.

dries’s picture

I'm also confused by '#builder', especially because we already have $form['#builder_function']. Gotta say, something doesn't feel quite right.

sun’s picture

AFAICS, #builder_function was only left for backwards compatibility. If @Dries in #27 was a "go, break those APIs!", then we can merge #builder_function + #builders into a new #entity_builders

dries’s picture

Personally, I think we can still break APIs while we're in ALPHA. We don't support HEAD2HEAD database updates either, so I don't see why we would treat code entirely different. So, let's break the API if (and only if) that makes it easier to understand. Once we're in beta, this will obviously change.

fago’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new9.04 KB

Ok, if that is an option I'd suggest to completely scratch #builder_function - it just made things unnecessarily complicated. #builder_function is only needed for doing wizard-like forms across different entity types - I'd say a rather rare scenario. Then we even have complete support in core, as it's not there for vocabularies or user accounts.

Instead let's use the naming convention FORM_NAME_submit_build_ENTITY_TYPE() which is mostly already established in core - that way we can scratch the ugly #builder_function property but still keep the option of the above mentioned rare use case. Attached patch does so + renames #builders to #entity_builders.

sun’s picture

Status: Needs review » Needs work
+++ includes/common.inc
@@ -6729,16 +6729,20 @@ function entity_form_field_validate($entity_type, $form, &$form_state) {
+ * modules may specify additional builder functions in $form['#entity_builder']

@@ -6753,6 +6757,13 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
+  // Invoke all specified builders for copying form values to entity properties.
+  if (isset($form['#entity_builder'])) {

The property should still be plural -- similar to #theme_wrappers

+++ modules/comment/comment.module
@@ -1979,7 +1978,7 @@ function comment_form($form, &$form_state, $comment) {
+  $comment = comment_form_submit_build_comment($form, $form_state);

+++ modules/node/node.pages.inc
@@ -305,7 +303,7 @@ function node_form_delete_submit($form, &$form_state) {
+  $node = node_form_submit_build_node($form, $form_state);

+++ modules/taxonomy/taxonomy.admin.inc
@@ -830,7 +829,7 @@ function taxonomy_form_term_submit($form, &$form_state) {
+  $term = taxonomy_form_term_submit_build_taxonomy_term($form, $form_state);

Shouldn't the function name pattern rather be

MODULE_form_submit_build_ENTITYTYPE()

?

At least, that would make sense to me, and is the case for all functions here, except taxonomy's, which repeats "term".

Powered by Dreditor.

fago’s picture

Status: Needs work » Needs review
StatusFileSize
new8.69 KB

@builders: Makes sense, fixed that.

@MODULE_form_submit_build_ENTITYTYPE()

I don't think so. The build function is specific to the form not to the entity. If - theoretically - there are multiple entity forms for a single entity type it might need multiple different build functions.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

fago’s picture

Issue tags: -D7 Form API challenge

#32: extensible.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work
Issue tags: +D7 Form API challenge

The last submitted patch, extensible.patch, failed testing.

fago’s picture

Status: Needs work » Needs review
StatusFileSize
new8.69 KB

I don't see how this should influence the javascript system/test - looks like a fragile test to me. -> Let's re-test it.

I re-rolled the patch to apply without offset.

fago’s picture

Status: Needs review » Reviewed & tested by the community

everything fine, so back to RTBC.

fago’s picture

Issue tags: +API change
-function taxonomy_form_term_submit_builder($form, &$form_state) {
+function taxonomy_form_term_submit_build_taxonomy_term($form, &$form_state) {

The patch renames taxonomy_form_term_submit_builder in order to match the naming convention as discussed in #30. Thus it is a function rename + finally totally deprecates $form['#builder_function'], so it is a small API change. Anyway it should be committed before the beta, so that we have finally figured out the way to do entity forms in core and contribs can follow that.

sun’s picture

#36: extensible-reroll.patch queued for re-testing.

sun’s picture

Version: 7.x-dev » 8.x-dev

Although badly needed, this is D8 material according to the rules (I had to learn today).

fago’s picture

Version: 8.x-dev » 7.x-dev

?? According to which rules?

* This is a bug that certainly needs to be fixed. It's impossible to extend an entity form (not being the node form) without using fields.
* Dries even encouraged us to finally scratch $form['#builder_function'] before the beta in #29.

catch’s picture

Issue tags: +Needs committer feedback

Per #29, this needs to be ruled in or out before we get to beta, adding tag.

effulgentsia’s picture

Status: Reviewed & tested by the community » Needs review

Given we're past beta1, and about to be in beta2, can #36 be modified to still be in consideration for D7?

effulgentsia’s picture

Also, another issue related to our lack of consistent and mature generic entity / entity-type-specific APIs: #949576: Missing hook_entity_view() and hook_entity_view_alter().

sun’s picture

Status: Needs review » Reviewed & tested by the community

This needs committer feedback, so RTBC. Also, this patch was ready a long time ago, and the sanity it introduces/restores is badly needed.

andypost’s picture

+1

sun’s picture

Issue tags: -Needs committer feedback

This patch is required for the http://drupal.org/project/title project (which turns entity labels/titles into fields, for many use-cases, but primarily to make them translatable).

Title module needs to act before field_attach_submit() is invoked -- which is exactly what this patch here does.

sun’s picture

#36: extensible-reroll.patch queued for re-testing.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

sun graciously spent some time with me in IRC on this patch.

So first off, I was concerned because Dries's comment back in #29 talked about breaking APIs pre-alpha. We're now way post-alpha, and even post-beta, really. But sun explained that the #builder_function property is a core-only construct and the only contributed modules that would be affected would be those actively overriding one of these core properties. For confirmation, I grepped the latest checkout of Drupal Commerce, which is the project I'm aware of making the fullest use of entities, and there's no #builder_function anywhere to be found. So I think this change is ok, BC-wise.

What I don't like about this patch is #entity_builders effectively creates an alternate hook_form_alter() for entities only. I still don't really understand why that's needed. But since Dries didn't raise this as a concern in his review, and since this is the last issue as part of #367006: [meta] Field attach API integration for entity forms is ungrokable to resolve in D7 and has buy-in from all the right people, I guess we'll go with it, and clean this whole mess up later in D8.

Committed to HEAD. Thanks!

rfay’s picture

Is the only API change in #38?

Please explain the impact of this change on existing calling code and let me know if it should be announced.

Thanks!

fago’s picture

Great to see this one finally getting in, thanks sun + webchick!

ad #49:
Yep, #builder_function is obsolete and I've seen no contrib setting it either. #entity_builders is no alternative to hook_form_alter(), in the contrary it is required in order to be able to properly use it on entity forms as you need a way to update the entity based on the form values.

ad #50:
Yes #38 + the API addition of $form['#entity_builders'], which allow modules extending entity forms to update the entity based upon their form values.

Status: Fixed » Closed (fixed)
Issue tags: -API change, -D7 Form API challenge

Automatically closed -- issue fixed for 2 weeks with no activity.