0

I am trying to find out how to redirect after I insert a new node. All the answers I have been able to find discuss $form_state->setRedirectUrl(); but this is a form redirect; not a proper redirect after insertion.

The difference is that when I submit the node form; I use hook_node_insert to create another entity based on information from the node being inserted. After the parent node is inserted; I want to redirect to a path based on the entity id of the new child entity.

In hook_node_insert (for the parent; i.e. original form submit)) I set a drupal_register_shutdown_function which properly saves my child entity and I then have the ->id() of the new (child) entity.

I have seen various examples using RedirectResponse, such as this:

$redirect = new RedirectResponse(Url::fromUserInput('/admin/commerce/orders/' . $order->id() . '/payments')->toString());;
$redirect->send();

but none of these work.

EDIT

to try to line this up with @4k4's suggestion, this is where I am at:

function my_module_node_insert($node) {
  $s = \Drupal::service('sia_general.redirects');
  $s->createPolicyRedirect($node);
}

in my Service:

class RedirectServices implements EventSubscriberInterface {

  static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE] = ['gotoPolicy'];
    return $events;
  }

  public function createPolicyRedirect($policy) {
    $this->policy = $policy;
  }

  public function gotoPolicy(FilterResponseEvent $event) {
    // We only care if this happened after policy was created.
    if (!$this->policy) {
      return;
    }

    $redirect = new RedirectResponse(Url::fromUserInput('/node/' . $this->policy->id() . '/edit')->toString());;
    $event->setResponse($redirect);
  }
}

when the Policy node is created; the hook_node_insert is called, which in turn calls the createPolicyRedirect() service and passes the newly created Policy object. After this it returns to the event subscriber which originally called the function which created the Policy. After that it hits the final pass of gotoPolicy(); but $this->policy is not set at this point.

Just to test that the redirect response worked as i hope; i tried setting a SESSION var in createPolicyRedirect(); but even that isn't set once i get to final gotoPolicy call.

EDIT #2

Still no luck and guessing due to the event handler within event handler that i am doing... so more context.

The complete flow is as follows:

  • create App entity
  • this adds items to Commerce cart
  • admin adds payment
  • PaymentComplete handler "places" the Order
  • OrderComplete handler loads the App and sets a Workflows field and then saves the App
  • during the App->save(), the change in Workflow triggers creating a Policy entity
  • this triggers my hook_node_insert($policy) (note: this is still within OrderComplete event handler).
  • in my insert hook i fire my 1st method above (which sets $this->policy)
  • i then return to finish the OrderComplete event handler
  • after the OrderComplete is finished; i hit my final run through the 2nd method (the KernelEvent); but by this time, $this->policy is no longer set.

My guess is that the ordercomplete does a page load before my kernel event is run (not sure that makes sense); so maybe this is a bug in Commerce; or at the very least i need to approach this differently, like alter what commerce is doing. Perhaps Drupal's event handling system isn't quite ready to handle situations this complex yet??

  • Instead of legacy drupal_register_shutdown_function, which seems to exist only for Batch API, you could use a symfony service, which provides two methods: One for creating the node and one for redirecting based on that node, registered as kernel response event subscriber. Similar to this service class, but you would call the first method from a hook, because you don't have an event you can subscribe to: drupal.stackexchange.com/a/291504/47547 – 4k4 Apr 2 '20 at 9:50
  • @4k4, thanks for the answer. I only understood about 1/2 of that. I would guess there is already a core event fired when a node is inserted (isn't there?) and I think you are suggesting writing an event subscription handler to act when that is fired. But, won't that put me at the same spot i am using hook_node_insert which i will need to know how to redirect. I'll check out the link you posted. – liquidcms Apr 2 '20 at 20:21
  • Ah, i think you are saying in my subscription to the event, i can set my response as a redirect response. And maybe you are suggesting there isn't a core event fired on node insert (why wouldn't there be??) so i would use my same hook_insert to fire the event that i have inserted a node. If i have that all correct; this might be simpler than Jaypan's idea of putting all the code into a submit handler. – liquidcms Apr 2 '20 at 20:24
  • Yes, the example service uses a class property to store the entity between the execution of the first method (in your case called by hook_insert directly) and the second method (KernelEvents::RESPONSE subscriber). – 4k4 Apr 3 '20 at 6:29
  • Spent a few hours and still haven't sorted it out. I get the 1st method part in the service but don't get how the 2nd method is an event subscriber; doesn't that need to be in an EventSubscriberInterface class, not a service class. Or perhaps service is just generic and i just extend it as an event subscriber? But even then, i don't see what calls the KernelEvent? From just adding this to another subscriber i had, i see it fires often; but not sure on what or why it would fire here. Maybe will make more sense in the morning. drupal_goto in shutdown function sure was simple.. lol – liquidcms Apr 3 '20 at 8:35
0

You could do a redirect in hook_node_insert(), however that would then prevent any other implementations of hook_node_insert() that come after your implementation from being executed.

I would instead suggest removing the code you've added in hook_node_insert(), and instead adding a custom submit handler to the node creation form. You can get the newly created node ID from $form_state->get('uid'). You can use that to load the newly created node, then create your node, and finally add the redirect in the form state, as you've mentioned in your post. This way no APIs are bypassed, and your redirect will happen after everything has been executed.

  • Hmm, interesting idea.. and certainly should be possible. I do have 100's of lines of code tied into hook_update and hook_insert for this; will need to see how complex moving this into a submit handler is. Thanks for the tip @Jaypan. – liquidcms Apr 2 '20 at 20:15
0

Proof of concept for redirecting on newly created nodes via a response event subscriber, as discussed in the comments:

Hook:

function mymodule_node_insert($node) {
  \Drupal::service('mymodule.my_response_subscriber')->setNode($node);  
}

Service class:

<?php

namespace Drupal\mymodule\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Url;

class MyResponseSubscriber implements EventSubscriberInterface {

  protected $node = NULL;

  public function setNode($node) {
    $this->node = $node;
  }

  public function kernelResponse(FilterResponseEvent $event) {
    if (!$this->node) {
      return;
    }
    $redirect = new RedirectResponse(Url::fromUserInput('/node/' . $this->node->id() . '/edit')->toString());
    $event->setResponse($redirect);    
  }

  public static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE][] = ['kernelResponse', 127];
    return $events;
  }

}

This opens the edit form after you've created a node, instead of rendering the node.

Recycling an existing subscriber this is working on my Drupal install. I've left the priority unchanged. You can test different priorities. For example, if you have set a destination query parameter try a priority <0 to override the destination.

Also debugging can be a challenge. Besides the main request Drupal fires a lot of other requests, especially for logged in users. So if you don't see a variable you expect to be set it's possible you are in a different request.

  • pretty much the same code as i have above.. after adjusting to be exactly as yours... same result. $this->node is not passed from method 1 to method 2.. i'll add a bit more context in my original post. my guess is my event handler buried in another event handler messes this up. – liquidcms Apr 7 '20 at 7:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.