Problem

hook_node_validate() and hook_node_submit() are hooks which can be considered already deprecated in d7: No other entity types have them and it's not best practice to use them: Instead, you should use a form element validation and a form builder callback to extract form values.
But in Drupal 8 hook_node_validate() is even worse: It bypasses the entity validation API and thus *may not* be used (e.g. think of REST). Therefor it should be removed. For validation you should use constraints with the validation API instead.

Proposed resolution

Remove hook_node_validate() and hook_node_submit() and convert their usages to constraints, added via hook_entity_base_field_info_alter()

Remaining tasks

Add minor fixes to the patch and write draft change notice.

User interface changes

-

API changes

hook_node_validate() and hook_node_submit() are removed.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fago’s picture

fago’s picture

Berdir’s picture

We had one case where we had to use this hook. It is currently not possible to add constraints to a configurable field. We have a field that is just a simple string field, but it has to be unique. It would be annoying if we have to define a field type just for that, as we then also have to alter widgets and formatters to make them available.

fago’s picture

The only usage in core is forum_node_validate(), which seems to add custom validation to taxonomy_forums. Thus, I guess that's what you are referring to.

Not being able to attach custom constraints to configurable fields is a generel problem though imo, or how would people attach custom validation logic to them else?

Berdir’s picture

My example was from a custom project, but yes, forum is similar.

Yes, it is a general problem, but it is also a blocker for removing this hook ;)

fago’s picture

Status: Active » Postponed

Right. Thus, this needs to be fixed first then. Postponed on #2247085: Constraints cannot be added to configurable fields

fago’s picture

Title: hook_node_validate() bypasses the entity validation API » [PP1] hook_node_validate() bypasses the entity validation API
Berdir’s picture

Personally, I'm fine with it either way, just wondering if we can justify an API change for removing it completely, or should we mark it as deprecated and say that you should use constraints instead?

We probably can, because security and stuff, but I still wanted to ask.

fago’s picture

Imo, it should be removed, because any implementation would have security implementations and is bad practice. But that's probably something a branch maintainer should decide.

larowlan’s picture

pretty sure forum_node_validate is removed in #2247085: Constraints cannot be added to configurable fields (party)

alexpott’s picture

Title: [PP1] hook_node_validate() bypasses the entity validation API » hook_node_validate() bypasses the entity validation API
Status: Postponed » Active

I agree with @fago - we should remove hook_node_validate() and hook_node_submit()

alexpott’s picture

Title: hook_node_validate() bypasses the entity validation API » Remove hook_node_validate() and hook_node_submit() becuase they bypass the entity API
Status: Active » Needs review
FileSize
5.56 KB

A starter for 10.

Berdir’s picture

+++ b/core/modules/menu_ui/menu_ui.module
@@ -354,14 +354,16 @@ function menu_ui_form_node_form_alter(&$form, FormStateInterface $form_state) {
     '#description' => t('Menu links with lower weights are displayed before links with higher weights.'),
   );
+  array_unshift($form['actions']['submit']['#submit'], 'menu_ui_form_node_form_submit');
...
-function menu_ui_node_submit(EntityInterface $node, $form, FormStateInterface $form_state) {
+function menu_ui_form_node_form_submit($form, FormStateInterface $form_state) {
+  $node = $form_state->getFormObject()->getEntity();
   if (!$form_state->isValueEmpty('menu')) {

not sure if submit belongs in this issue.

But when we touch it, then should be able to clean this up so that we don't have to attach $node->menu to the node anymore if we add the submit callback last and not at the beginning?

klausi’s picture

Title: Remove hook_node_validate() and hook_node_submit() becuase they bypass the entity API » Remove hook_node_validate() and hook_node_submit() because they bypass the entity API
Issue summary: View changes
Status: Needs review » Needs work

I searched for node_submit and there is core/modules/node/src/Tests/NodeSaveTest.php with a strange "Function node_submit() preserves user ID", so we should probably also fix that comment in this patch.

I searched for node_validate and core/modules/system/entity.api.php still mentions it, so that should be fixed, too.

But otherwise the patch looks good. I think we should not try to also fix $node->menu in this critical issue, that can be a follow-up.

And we will need a change notice draft with instructions what developers should use instead.

Berdir’s picture

Status: Needs work » Needs review
FileSize
15.62 KB
10.88 KB

Was very hard to stop with the menu_ui cleanup ;)

larowlan’s picture

+++ b/core/modules/menu_ui/menu_ui.module
@@ -127,54 +127,33 @@ function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginIn
+function menu_ui_node_save(NodeInterface $node, array $definition) {

@@ -196,70 +175,73 @@ function menu_ui_node_predelete(EntityInterface $node) {
+function menu_ui_get_definition(NodeInterface $node) {

this so feels like we want a hook_entity_base_field_info like path module and define a 'menu_link' ER field. Perhaps a follow up? Would be so much cleaner.

yched’s picture

  1. +++ b/core/modules/node/src/NodeForm.php
    @@ -293,14 +293,6 @@ public function validate(array $form, FormStateInterface $form_state) {
    -    // Invoke hook_node_validate() for validation needed by modules.
    -    // Can't use \Drupal::moduleHandler()->invokeAll(), because $form_state must
    -    // be receivable by reference.
    -    foreach (\Drupal::moduleHandler()->getImplementations('node_validate') as $module) {
    -      $function = $module . '_node_validate';
    -      $function($node, $form, $form_state);
    -    }
    

    Yay ! Any chance we can stop doing $node = $this->buildEntity($form, $form_state); a couple lines above then ?
    See #1768526: NodeFormController::validate() calls buildEntity() twice :-)

  2. +++ b/core/modules/menu_ui/menu_ui.module
    @@ -127,54 +127,33 @@ function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginIn
    + * Helper function to save a menu link for a node.
    ...
    +function menu_ui_node_save(NodeInterface $node, array $definition) {
    

    Nitpick : if it's a helper, can we prefix it with '_' ?
    The current name still looks damn close to a hook implementation :-)

  3. +++ b/core/modules/menu_ui/menu_ui.module
    @@ -196,70 +175,73 @@ function menu_ui_node_predelete(EntityInterface $node) {
    +function menu_ui_get_definition(NodeInterface $node) {
    

    Nitpick: the function name isn't very clear on "definition of what ?"

Berdir’s picture

3. That's kind of the problem... menu_ui_get_weird_definition_that_is_somehow_supposed_to_represent_a_menu_link() ? ;) I kept that "definition" thing, but it really is pretty strange. menu_ui_get_menu_link_definition() doesn't make it much clearer? maybe menu_link_defaults() would be better? (which resembles the hook that we had some time ago ;))

Berdir’s picture

FileSize
18.89 KB
8.62 KB

1. Hm. We still have the changed validation there, but we should be able to fix that by returning $entity in parent::validate(), just like we already do for save()?
2. Added _, also renamed $definition there to $values.
3. Renamed to menu_ui_get_menu_link_defaults() as per my last suggestion, also renamed the relevant variables. Even more unrelated changes in this issue :)

yched’s picture

We still have the changed validation there, but we should be able to fix that by returning $entity in parent::validate(), just like we already do for save()?

Right, I missed the $node->getChangedTime() at the very end of the line :-/

That sound like a plan, yes, it feels silly to have each override of validate() re-do a buildEntity() because the results are lost.
Probably more a task for #1768526: NodeFormController::validate() calls buildEntity() twice than for this patch here, though :-)

klausi’s picture

Status: Needs review » Needs work

Looks good, I had to review with git diff --color-words, most of the changes are just variable renaming. One nitpick left then we only need the change notice draft.

+++ b/core/modules/system/entity.api.php
@@ -152,7 +152,6 @@
  * Some specific entity types have additional hooks that are run during
  * various steps in the process:
- * - Node entities: hook_node_validate() and hook_submit().

Now the sentence above does not make sense with the ending ":" ;-)

Berdir’s picture

Status: Needs work » Needs review
FileSize
19 KB
553 bytes

Hehe, nice.

Some specific entity types have additional hooks...

No, they don't. At least nothing that's worth documenting here. So I just removed it...

Started with a CR: https://www.drupal.org/node/2420295

yched’s picture

Status: Needs review » Reviewed & tested by the community

Yay. Looks RTBC to me then.

klausi’s picture

RTBC+1, I updated the change notice with an example D7 version for hook_node_submit() (which does not make completely sense, but it should work to demonstrate the conversion).

fago’s picture

I don't think a #submit builder is a correct alternative to the #entity_builder, i.e. it would be missed from EntityForm::buildEntity() and thus during validation. Thus, I removed that "alternative" from the change record.

Patch looks good to me as well.

this so feels like we want a hook_entity_base_field_info like path module and define a 'menu_link' ER field. Perhaps a follow up? Would be so much cleaner.

Agreed that this code use more clean-up, but this can be a non-critical follow-up and should not hold up this!

Berdir’s picture

I don't think a #submit builder is a correct alternative to the #entity_builder, i.e. it would be missed from EntityForm::buildEntity() and thus during validation. Thus, I removed that "alternative" from the change record.

I disagree. Yes, #submit is not an alternative to #entity, but that is not what I said (or meant). But that hook_node_submit() should be converted to entity builder or #submit. This patch uses #submit, and it is correct in doing so. If you want to *do* something with the created entity, use #submit, if you want to help building it before saving, use #entity_builder.

If that was not clear enough then we need to improve it, but removing it is wrong.

fago’s picture

I see - right, there a valid cases and some of the previous hook_node_submit() cases might fit there. So it was the CR which hat a wrong example then and did not point out when to use what. I tried to improve this now, please check it.

catch’s picture

Status: Reviewed & tested by the community » Fixed

OK. Committed/pushed to 8.0.x, thanks!

I think we have an issue somewhere to look at the menu links on node form (or that's one of the intended follow-ups from the NJ sprint at least), so we can revisit that code in there.

  • catch committed 824fef8 on 8.0.x
    Issue #2406103 by Berdir, alexpott: Remove hook_node_validate() and...
mikey_p’s picture

I think this is the issue for the menu_links on the node form: #2315773: Create a menu link field type/widget/formatter.

Berdir’s picture

Thanks, restored and extend my initial example in the submit callback, to get the $node object and doing something with it, which is I think something that might be more common than getting values from $form_state, and people know how to do that.

fago’s picture

Thanks, that looks good - I've fixed the CR version to be the next beta instead of the current one and published it.

Status: Fixed » Closed (fixed)

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

cilefen’s picture